From 6100b619c6d970a3f38d0157b529d2a94a010dae Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 16 Nov 2023 10:42:58 +1100 Subject: [PATCH] Added some more prepared request unit tests --- .../PreparedRequestOnionRequestsSpec.swift | 352 +++++++++++++++++- 1 file changed, 336 insertions(+), 16 deletions(-) diff --git a/SessionSnodeKitTests/Networking/PreparedRequestOnionRequestsSpec.swift b/SessionSnodeKitTests/Networking/PreparedRequestOnionRequestsSpec.swift index 97ccd84b0..fa4248db8 100644 --- a/SessionSnodeKitTests/Networking/PreparedRequestOnionRequestsSpec.swift +++ b/SessionSnodeKitTests/Networking/PreparedRequestOnionRequestsSpec.swift @@ -76,12 +76,34 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { expect(response).to(beNil()) } + // MARK: ------ can return a cached response + it("can return a cached response") { + var response: (info: ResponseInfoType, data: Int)? + + preparedRequest = HTTP.PreparedRequest.cached( + 100, + endpoint: TestEndpoint.endpoint1 + ) + + preparedRequest + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(response).toNot(beNil()) + expect(response?.data).to(equal(100)) + expect(error).to(beNil()) + } + // MARK: ---- and handling events context("and handling events") { @TestState var didReceiveSubscription: Bool! = false @TestState var didReceiveCancel: Bool! = false @TestState var receivedOutput: (ResponseInfoType, Int)? = nil @TestState var receivedCompletion: Subscribers.Completion? = nil + @TestState var multiDidReceiveSubscription: [Bool]! = [] + @TestState var multiReceivedCompletion: [Subscribers.Completion]! = [] // MARK: ------ calls receiveSubscription correctly it("calls receiveSubscription correctly") { @@ -90,7 +112,7 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { receiveSubscription: { didReceiveSubscription = true } ) .send(using: dependencies) - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(didReceiveSubscription).to(beTrue()) } @@ -102,7 +124,7 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { receiveOutput: { info, output in receivedOutput = (info, output) } ) .send(using: dependencies) - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(receivedOutput).toNot(beNil()) } @@ -114,7 +136,7 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { receiveCompletion: { result in receivedCompletion = result } ) .send(using: dependencies) - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(receivedCompletion).toNot(beNil()) } @@ -129,7 +151,7 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { .handleEvents( receiveSubscription: { $0.cancel() } ) - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(didReceiveCancel).to(beTrue()) } @@ -142,44 +164,332 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { receiveCompletion: { result in receivedCompletion = result } ) .send(using: dependencies) - .sinkUntilComplete() + .sinkAndStore(in: &disposables) expect(didReceiveSubscription).to(beTrue()) expect(receivedCompletion).toNot(beNil()) } + + // MARK: ------ supports multiple handleEvents calls + it("supports multiple handleEvents calls") { + preparedRequest + .handleEvents( + receiveSubscription: { multiDidReceiveSubscription.append(true) }, + receiveCompletion: { result in multiReceivedCompletion.append(result) } + ) + .handleEvents( + receiveSubscription: { multiDidReceiveSubscription.append(true) }, + receiveCompletion: { result in multiReceivedCompletion.append(result) } + ) + .handleEvents( + receiveSubscription: { multiDidReceiveSubscription.append(true) }, + receiveCompletion: { result in multiReceivedCompletion.append(result) } + ) + .send(using: dependencies) + .sinkAndStore(in: &disposables) + + expect(multiDidReceiveSubscription).to(equal([true, true, true])) + expect(multiReceivedCompletion.count).to(equal(3)) + } } + // MARK: ---- and transforming the result context("and transforming the result") { @TestState var receivedOutput: (ResponseInfoType, String)? = nil + @TestState var didReceiveSubscription: Bool! = false + @TestState var receivedCompletion: Subscribers.Completion? = nil // MARK: ------ successfully transforms the result it("successfully transforms the result") { preparedRequest .map { _, output -> String in "\(output)" } .send(using: dependencies) - .sinkUntilComplete( - receiveValue: { info, output in receivedOutput = (info, output) } - ) + .handleEvents(receiveOutput: { info, output in receivedOutput = (info, output) }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) expect(receivedOutput?.1).to(equal("1")) } + // MARK: ------ successfully transforms multiple times + it("successfully transforms multiple times") { + var result: TestType? + + preparedRequest + .map { _, output -> TestType in + TestType(intValue: output, stringValue: "Test", optionalStringValue: nil) + } + .map { _, output -> TestType in + TestType( + intValue: output.intValue, + stringValue: output.stringValue, + optionalStringValue: "AnotherString" + ) + } + .send(using: dependencies) + .handleEvents(receiveOutput: { _, output in result = output }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(result?.intValue).to(equal(1)) + expect(result?.stringValue).to(equal("Test")) + expect(result?.optionalStringValue).to(equal("AnotherString")) + } + // MARK: ------ will fail if the transformation throws it("will fail if the transformation throws") { preparedRequest .tryMap { _, output -> String in throw HTTPError.generic } .send(using: dependencies) - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let failureError): error = failureError - } - } - ) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) expect(error).to(matchError(HTTPError.generic)) } + + // MARK: ------ works with a cached response + it("works with a cached response") { + var response: (info: ResponseInfoType, data: String)? + + preparedRequest = HTTP.PreparedRequest.cached( + 100, + endpoint: TestEndpoint.endpoint1 + ) + + preparedRequest + .map { _, output -> String in "\(output)" } + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(response).toNot(beNil()) + expect(response?.data).to(equal("100")) + expect(error).to(beNil()) + } + + // MARK: ------ works with the event handling + it("works with the event handling") { + preparedRequest + .map { _, output -> String in "\(output)" } + .handleEvents( + receiveSubscription: { didReceiveSubscription = true }, + receiveCompletion: { result in receivedCompletion = result } + ) + .send(using: dependencies) + .sinkAndStore(in: &disposables) + + expect(didReceiveSubscription).to(beTrue()) + expect(receivedCompletion).toNot(beNil()) + } + } + + // MARK: ---- a batch request + context("a batch request") { + // MARK: ---- with a BatchResponseMap + context("with a BatchResponseMap") { + @TestState var subRequest1: Request! = Request( + method: .post, + server: "https://www.oxen.io", + endpoint: TestEndpoint.endpoint1, + x25519PublicKey: "" + ) + @TestState var subRequest2: Request! = Request( + method: .post, + server: "https://www.oxen.io", + endpoint: TestEndpoint.endpoint2, + x25519PublicKey: "" + ) + @TestState var preparedBatchRequest: HTTP.PreparedRequest>! = { + let request = Request( + method: .post, + server: "https://www.oxen.io", + endpoint: TestEndpoint.batch, + x25519PublicKey: "", + body: HTTP.BatchRequest( + requests: [ + HTTP.PreparedRequest( + request: subRequest1, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ), + HTTP.PreparedRequest( + request: subRequest2, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ) + ] + ) + ) + + return HTTP.PreparedRequest( + request: request, + urlRequest: try! request.generateUrlRequest(using: dependencies), + responseType: HTTP.BatchResponseMap.self, + retryCount: 0, + timeout: 10 + ) + }() + @TestState var response: (info: ResponseInfoType, data: HTTP.BatchResponseMap)? + @TestState var receivedOutput: (ResponseInfoType, String)? = nil + @TestState var didReceiveSubscription: Bool! = false + @TestState var receivedCompletion: Subscribers.Completion? = nil + + beforeEach { + mockNetwork + .when { $0.send(.selectedNetworkRequest(any(), to: any(), with: any(), using: any())) } + .thenReturn( + MockNetwork.batchResponseData(with: [ + (endpoint: TestEndpoint.endpoint1, data: TestType.mockBatchSubResponse()), + (endpoint: TestEndpoint.endpoint2, data: TestType.mockBatchSubResponse()) + ]) + ) + } + + // MARK: ---- triggers sending correctly + it("triggers sending correctly") { + preparedBatchRequest + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(response).toNot(beNil()) + expect(response?.data.count).to(equal(2)) + expect((response?.data.data[.endpoint1] as? HTTP.BatchSubResponse)?.body) + .to(equal(TestType(intValue: 100, stringValue: "Test", optionalStringValue: nil))) + expect((response?.data.data[.endpoint2] as? HTTP.BatchSubResponse)?.body) + .to(equal(TestType(intValue: 100, stringValue: "Test", optionalStringValue: nil))) + expect(error).to(beNil()) + } + + // MARK: ------ works with transformations + it("works with transformations") { + preparedBatchRequest + .map { info, _ in receivedOutput = (info, "Test") } + .send(using: dependencies) + .sinkAndStore(in: &disposables) + + expect(receivedOutput?.1).to(equal("Test")) + } + + // MARK: ------ supports transformations on subrequests + it("supports transformations on subrequests") { + preparedBatchRequest = { + let request = Request( + method: .post, + server: "https://www.oxen.io", + endpoint: TestEndpoint.batch, + x25519PublicKey: "", + body: HTTP.BatchRequest( + requests: [ + HTTP.PreparedRequest( + request: subRequest1, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ) + .map { _, _ in "Test" }, + HTTP.PreparedRequest( + request: subRequest2, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ) + ] + ) + ) + + return HTTP.PreparedRequest( + request: request, + urlRequest: try! request.generateUrlRequest(using: dependencies), + responseType: HTTP.BatchResponseMap.self, + retryCount: 0, + timeout: 10 + ) + }() + + preparedBatchRequest + .send(using: dependencies) + .handleEvents(receiveOutput: { result in response = result }) + .mapError { error.setting(to: $0) } + .sinkAndStore(in: &disposables) + + expect(response).toNot(beNil()) + expect(response?.data.count).to(equal(2)) + expect((response?.data.data[.endpoint1] as? HTTP.BatchSubResponse)?.body) + .to(equal("Test")) + expect((response?.data.data[.endpoint2] as? HTTP.BatchSubResponse)?.body) + .to(equal(TestType(intValue: 100, stringValue: "Test", optionalStringValue: nil))) + expect(error).to(beNil()) + } + + // MARK: ------ works with the event handling + it("works with the event handling") { + preparedBatchRequest + .handleEvents( + receiveSubscription: { didReceiveSubscription = true }, + receiveCompletion: { result in receivedCompletion = result } + ) + .send(using: dependencies) + .sinkAndStore(in: &disposables) + + expect(didReceiveSubscription).to(beTrue()) + expect(receivedCompletion).toNot(beNil()) + } + + // MARK: ------ supports event handling on sub requests + it("supports event handling on sub requests") { + preparedBatchRequest = { + let request = Request( + method: .post, + server: "https://www.oxen.io", + endpoint: TestEndpoint.batch, + x25519PublicKey: "", + body: HTTP.BatchRequest( + requests: [ + HTTP.PreparedRequest( + request: subRequest1, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ) + .handleEvents( + receiveCompletion: { result in receivedCompletion = result } + ), + HTTP.PreparedRequest( + request: subRequest2, + urlRequest: try! subRequest1.generateUrlRequest(using: dependencies), + responseType: TestType.self, + retryCount: 0, + timeout: 10 + ) + ] + ) + ) + + return HTTP.PreparedRequest( + request: request, + urlRequest: try! request.generateUrlRequest(using: dependencies), + responseType: HTTP.BatchResponseMap.self, + retryCount: 0, + timeout: 10 + ) + }() + + preparedBatchRequest + .send(using: dependencies) + .sinkAndStore(in: &disposables) + + expect(receivedCompletion).toNot(beNil()) + } } } } @@ -191,6 +501,7 @@ class PreparedRequestOnionRequestsSpec: QuickSpec { fileprivate enum TestEndpoint: EndpointType { case endpoint1 case endpoint2 + case batch static var name: String { "TestEndpoint" } static var batchRequestVariant: HTTP.BatchRequest.Child.Variant { .storageServer } @@ -200,6 +511,15 @@ fileprivate enum TestEndpoint: EndpointType { switch self { case .endpoint1: return "endpoint1" case .endpoint2: return "endpoint2" + case .batch: return "batch" } } } + +fileprivate struct TestType: Codable, Equatable, Mocked { + static var mockValue: TestType { TestType(intValue: 100, stringValue: "Test", optionalStringValue: nil) } + + let intValue: Int + let stringValue: String + let optionalStringValue: String? +}