// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import Combine import Quick import Nimble @testable import SessionUtilitiesKit class BatchResponseSpec: QuickSpec { struct TestType: Codable, Equatable { let stringValue: String } struct TestType2: Codable, Equatable { let intValue: Int let stringValue2: String } // MARK: - Spec override func spec() { // MARK: - HTTP.BatchSubResponse describe("an HTTP.BatchSubResponse") { context("when decoding") { it("decodes correctly") { let jsonString: String = """ { "code": 200, "headers": { "testKey": "testValue" }, "body": { "stringValue": "testValue" } } """ let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( HTTP.BatchSubResponse.self, from: jsonString.data(using: .utf8)! ) expect(subResponse).toNot(beNil()) expect(subResponse?.body).toNot(beNil()) } it("decodes with invalid body data") { let jsonString: String = """ { "code": 200, "headers": { "testKey": "testValue" }, "body": "Hello!!!" } """ let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( HTTP.BatchSubResponse.self, from: jsonString.data(using: .utf8)! ) expect(subResponse).toNot(beNil()) } it("flags invalid body data as invalid") { let jsonString: String = """ { "code": 200, "headers": { "testKey": "testValue" }, "body": "Hello!!!" } """ let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( HTTP.BatchSubResponse.self, from: jsonString.data(using: .utf8)! ) expect(subResponse).toNot(beNil()) expect(subResponse?.body).to(beNil()) expect(subResponse?.failedToParseBody).to(beTrue()) } it("does not flag a missing or invalid optional body as invalid") { let jsonString: String = """ { "code": 200, "headers": { "testKey": "testValue" }, } """ let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( HTTP.BatchSubResponse.self, from: jsonString.data(using: .utf8)! ) expect(subResponse).toNot(beNil()) expect(subResponse?.body).to(beNil()) expect(subResponse?.failedToParseBody).to(beFalse()) } it("does not flag a NoResponse body as invalid") { let jsonString: String = """ { "code": 200, "headers": { "testKey": "testValue" }, } """ let subResponse: HTTP.BatchSubResponse? = try? JSONDecoder().decode( HTTP.BatchSubResponse.self, from: jsonString.data(using: .utf8)! ) expect(subResponse).toNot(beNil()) expect(subResponse?.body).to(beNil()) expect(subResponse?.failedToParseBody).to(beFalse()) } } } // MARK: - Convenience // MARK: --Decodable describe("a Decodable") { it("decodes correctly") { let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)! let result: TestType? = try? TestType.decoded(from: jsonData) expect(result).to(equal(TestType(stringValue: "testValue"))) } } // MARK: - --Combine describe("a (ResponseInfoType, Data?) Publisher") { var responseInfo: ResponseInfoType! var testType: TestType! var testType2: TestType2! var data: Data! beforeEach { responseInfo = HTTP.ResponseInfo(code: 200, headers: [:]) testType = TestType(stringValue: "test1") testType2 = TestType2(intValue: 123, stringValue2: "test2") data = """ [\([ try! JSONEncoder().encode( HTTP.BatchSubResponse( code: 200, headers: [:], body: testType, failedToParseBody: false ) ), try! JSONEncoder().encode( HTTP.BatchSubResponse( code: 200, headers: [:], body: testType2, failedToParseBody: false ) ) ] .map { String(data: $0, encoding: .utf8)! } .joined(separator: ","))] """.data(using: .utf8)! } it("decodes valid data correctly") { var result: HTTP.BatchResponse? Just((responseInfo, data)) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) .sinkUntilComplete( receiveValue: { result = $0 } ) expect(result).toNot(beNil()) expect((result?.responses[0] as? HTTP.BatchSubResponse)?.body) .to(equal(testType)) expect((result?.responses[1] as? HTTP.BatchSubResponse)?.body) .to(equal(testType2)) } it("fails if there is no data") { var error: Error? Just((responseInfo, nil)) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: []) .mapError { error.setting(to: $0) } .sinkUntilComplete() expect(error?.localizedDescription) .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the data is not JSON") { var error: Error? Just((responseInfo, Data([1, 2, 3]))) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: []) .mapError { error.setting(to: $0) } .sinkUntilComplete() expect(error?.localizedDescription) .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the data is not a JSON array") { var error: Error? Just((responseInfo, "{}".data(using: .utf8))) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: []) .mapError { error.setting(to: $0) } .sinkUntilComplete() expect(error?.localizedDescription) .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if the JSON array does not have the same number of items as the expected types") { var error: Error? Just((responseInfo, data)) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) .mapError { error.setting(to: $0) } .sinkUntilComplete() expect(error?.localizedDescription) .to(equal(HTTPError.parsingFailed.localizedDescription)) } it("fails if one of the JSON array values fails to decode") { data = """ [\([ try! JSONEncoder().encode( HTTP.BatchSubResponse( code: 200, headers: [:], body: testType, failedToParseBody: false ) ) ] .map { String(data: $0, encoding: .utf8)! } .joined(separator: ",")),{"test": "test"}] """.data(using: .utf8)! var error: Error? Just((responseInfo, data)) .setFailureType(to: Error.self) .eraseToAnyPublisher() .decoded(as: [ HTTP.BatchSubResponse.self, HTTP.BatchSubResponse.self ]) .mapError { error.setting(to: $0) } .sinkUntilComplete() expect(error?.localizedDescription) .to(equal(HTTPError.parsingFailed.localizedDescription)) } } } }