|
|
|
@ -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<Int>.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<Error>? = nil
|
|
|
|
|
@TestState var multiDidReceiveSubscription: [Bool]! = []
|
|
|
|
|
@TestState var multiReceivedCompletion: [Subscribers.Completion<Error>]! = []
|
|
|
|
|
|
|
|
|
|
// 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<Error>? = 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<Int>.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<NoBody, TestEndpoint>! = Request<NoBody, TestEndpoint>(
|
|
|
|
|
method: .post,
|
|
|
|
|
server: "https://www.oxen.io",
|
|
|
|
|
endpoint: TestEndpoint.endpoint1,
|
|
|
|
|
x25519PublicKey: ""
|
|
|
|
|
)
|
|
|
|
|
@TestState var subRequest2: Request<NoBody, TestEndpoint>! = Request<NoBody, TestEndpoint>(
|
|
|
|
|
method: .post,
|
|
|
|
|
server: "https://www.oxen.io",
|
|
|
|
|
endpoint: TestEndpoint.endpoint2,
|
|
|
|
|
x25519PublicKey: ""
|
|
|
|
|
)
|
|
|
|
|
@TestState var preparedBatchRequest: HTTP.PreparedRequest<HTTP.BatchResponseMap<TestEndpoint>>! = {
|
|
|
|
|
let request = Request<HTTP.BatchRequest, TestEndpoint>(
|
|
|
|
|
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<TestEndpoint>.self,
|
|
|
|
|
retryCount: 0,
|
|
|
|
|
timeout: 10
|
|
|
|
|
)
|
|
|
|
|
}()
|
|
|
|
|
@TestState var response: (info: ResponseInfoType, data: HTTP.BatchResponseMap<TestEndpoint>)?
|
|
|
|
|
@TestState var receivedOutput: (ResponseInfoType, String)? = nil
|
|
|
|
|
@TestState var didReceiveSubscription: Bool! = false
|
|
|
|
|
@TestState var receivedCompletion: Subscribers.Completion<Error>? = 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<TestType>)?.body)
|
|
|
|
|
.to(equal(TestType(intValue: 100, stringValue: "Test", optionalStringValue: nil)))
|
|
|
|
|
expect((response?.data.data[.endpoint2] as? HTTP.BatchSubResponse<TestType>)?.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<HTTP.BatchRequest, TestEndpoint>(
|
|
|
|
|
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<TestEndpoint>.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<String>)?.body)
|
|
|
|
|
.to(equal("Test"))
|
|
|
|
|
expect((response?.data.data[.endpoint2] as? HTTP.BatchSubResponse<TestType>)?.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<HTTP.BatchRequest, TestEndpoint>(
|
|
|
|
|
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<TestEndpoint>.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?
|
|
|
|
|
}
|
|
|
|
|