Fixing the broken unit tests, resolved the remaining TODOs

# Conflicts:
#	Session.xcodeproj/project.pbxproj
#	SessionMessagingKitTests/Open Groups/Models/BatchRequestInfoSpec.swift
#	SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift
#	SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift
#	SessionMessagingKitTests/_TestUtilities/TestOnionRequestAPI.swift
#	SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift
#	SessionUtilitiesKit/Networking/BatchResponse.swift
#	SessionUtilitiesKitTests/Networking/BatchResponseSpec.swift
pull/856/head
Morgan Pretty 2 years ago
parent 5033738994
commit ca4ce52402

@ -809,6 +809,7 @@
FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; }; FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; };
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; }; FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; };
FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; }; FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; };
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; };
FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */; }; FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */; };
FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; }; FDA8EB00280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */; };
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; }; FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */; };
@ -1919,6 +1920,7 @@
FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = "<group>"; }; FD8ECFA0293D8FDD00C0D1BB /* URLResponse+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLResponse+Utilities.swift"; sourceTree = "<group>"; };
FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; }; FD90040E2818AB6D00ABAAF6 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; };
FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; }; FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = "<group>"; };
FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = "<group>"; }; FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedMessageSendsJob.swift; sourceTree = "<group>"; };
FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = "<group>"; }; FDA8EAFF280E8D58002B68E5 /* FailedAttachmentDownloadsJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAttachmentDownloadsJob.swift; sourceTree = "<group>"; };
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = "<group>"; }; FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = "<group>"; };
@ -3968,7 +3970,7 @@
children = ( children = (
FD37EA1228AB3F60003AE748 /* Database */, FD37EA1228AB3F60003AE748 /* Database */,
FD83B9B927CF20A5005E1583 /* General */, FD83B9B927CF20A5005E1583 /* General */,
FD8ECF832934507500C0D1BB /* Networking */, FD9B30F1293EA0AF008DEE3E /* Networking */,
); );
path = SessionUtilitiesKitTests; path = SessionUtilitiesKitTests;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4065,6 +4067,14 @@
path = JobRunner; path = JobRunner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FD9B30F1293EA0AF008DEE3E /* Networking */ = {
isa = PBXGroup;
children = (
FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */,
);
path = Networking;
sourceTree = "<group>";
};
FDC2909227D710A9005DAE71 /* Types */ = { FDC2909227D710A9005DAE71 /* Types */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -5916,6 +5926,7 @@
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */, FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */, FD8ECF852934508B00C0D1BB /* BatchResponseSpec.swift in Sources */,
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */, FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */, FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */, FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */, FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,

@ -53,6 +53,7 @@ public enum PushRegistrationError: Error {
return registerUserNotificationSettings() return registerUserNotificationSettings()
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.receive(on: DispatchQueue.main) // MUST be on main thread
.flatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in .flatMap { _ -> AnyPublisher<(pushToken: String, voipToken: String), Error> in
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
return Fail(error: PushRegistrationError.pushNotSupported(description: "Push not supported on simulators")) return Fail(error: PushRegistrationError.pushNotSupported(description: "Push not supported on simulators"))

@ -188,7 +188,6 @@ extension SyncPushTokensJob {
} }
.sinkUntilComplete( .sinkUntilComplete(
receiveCompletion: { result in receiveCompletion: { result in
// TODO: Test these are called correctly
switch result { switch result {
case .finished: break case .finished: break
case .failure(let error): failure(error) case .failure(let error): failure(error)

@ -26,8 +26,7 @@ public protocol OGMCacheType {
// MARK: - OpenGroupManager // MARK: - OpenGroupManager
@objc(SNOpenGroupManager) public final class OpenGroupManager {
public final class OpenGroupManager: NSObject {
// MARK: - Cache // MARK: - Cache
public class Cache: OGMCacheType { public class Cache: OGMCacheType {
@ -61,7 +60,7 @@ public final class OpenGroupManager: NSObject {
// MARK: - Variables // MARK: - Variables
@objc public static let shared: OpenGroupManager = OpenGroupManager() public static let shared: OpenGroupManager = OpenGroupManager()
/// Note: This should not be accessed directly but rather via the 'OGMDependencies' type /// Note: This should not be accessed directly but rather via the 'OGMDependencies' type
fileprivate let mutableCache: Atomic<OGMCacheType> = Atomic(Cache()) fileprivate let mutableCache: Atomic<OGMCacheType> = Atomic(Cache())

@ -1,7 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit import Combine
import SessionSnodeKit import SessionSnodeKit
import SessionUtilitiesKit import SessionUtilitiesKit
@ -19,97 +19,117 @@ class BatchRequestInfoSpec: QuickSpec {
// MARK: - Spec // MARK: - Spec
override func spec() { override func spec() {
// MARK: - BatchSubRequest // MARK: - BatchRequest.Child
describe("a BatchRequest.Child") { describe("a BatchRequest.Child") {
var subRequest: OpenGroupAPI.BatchRequest.Child! var request: OpenGroupAPI.BatchRequest!
context("when initializing") { context("when initializing") {
it("sets the headers to nil if there aren't any") { it("sets the headers to nil if there aren't any") {
subRequest = OpenGroupAPI.BatchRequest.Child( request = OpenGroupAPI.BatchRequest(
request: Request<NoBody, OpenGroupAPI.Endpoint>( requests: [
server: "testServer", OpenGroupAPI.BatchRequest.Info(
endpoint: .batch request: Request<NoBody, OpenGroupAPI.Endpoint>(
) server: "testServer",
endpoint: .batch
)
)
]
) )
expect(subRequest.headers).to(beNil()) expect(request.requests.first?.headers).to(beNil())
} }
it("converts the headers to HTTP headers") { it("converts the headers to HTTP headers") {
subRequest = OpenGroupAPI.BatchRequest.Child( request = OpenGroupAPI.BatchRequest(
request: Request<NoBody, OpenGroupAPI.Endpoint>( requests: [
method: .get, OpenGroupAPI.BatchRequest.Info(
server: "testServer", request: Request<NoBody, OpenGroupAPI.Endpoint>(
endpoint: .batch, method: .get,
queryParameters: [:], server: "testServer",
headers: [.authorization: "testAuth"], endpoint: .batch,
body: nil queryParameters: [:],
) headers: [.authorization: "testAuth"],
body: nil
)
)
]
) )
expect(subRequest.headers).to(equal(["Authorization": "testAuth"])) expect(request.requests.first?.headers).to(equal(["Authorization": "testAuth"]))
} }
} }
context("when encoding") { context("when encoding") {
it("successfully encodes a string body") { it("successfully encodes a string body") {
subRequest = OpenGroupAPI.BatchRequest.Child( request = OpenGroupAPI.BatchRequest(
request: Request<String, OpenGroupAPI.Endpoint>( requests: [
method: .get, OpenGroupAPI.BatchRequest.Info(
server: "testServer", request: Request<String, OpenGroupAPI.Endpoint>(
endpoint: .batch, method: .get,
queryParameters: [:], server: "testServer",
headers: [:], endpoint: .batch,
body: "testBody" queryParameters: [:],
) headers: [:],
body: "testBody"
)
)
]
) )
let subRequestData: Data = try! JSONEncoder().encode(subRequest) let childRequestData: Data = try! JSONEncoder().encode(request.requests[0])
let subRequestString: String? = String(data: subRequestData, encoding: .utf8) let childRequestString: String? = String(data: childRequestData, encoding: .utf8)
expect(subRequestString) expect(childRequestString)
.to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}")) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}"))
} }
it("successfully encodes a byte body") { it("successfully encodes a byte body") {
subRequest = OpenGroupAPI.BatchRequest.Child( request = OpenGroupAPI.BatchRequest(
request: Request<[UInt8], OpenGroupAPI.Endpoint>( requests: [
method: .get, OpenGroupAPI.BatchRequest.Info(
server: "testServer", request: Request<[UInt8], OpenGroupAPI.Endpoint>(
endpoint: .batch, method: .get,
queryParameters: [:], server: "testServer",
headers: [:], endpoint: .batch,
body: [1, 2, 3] queryParameters: [:],
) headers: [:],
body: [1, 2, 3]
)
)
]
) )
let subRequestData: Data = try! JSONEncoder().encode(subRequest) let childRequestData: Data = try! JSONEncoder().encode(request.requests[0])
let subRequestString: String? = String(data: subRequestData, encoding: .utf8) let childRequestString: String? = String(data: childRequestData, encoding: .utf8)
expect(subRequestString) expect(childRequestString)
.to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}")) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}"))
} }
it("successfully encodes a JSON body") { it("successfully encodes a JSON body") {
subRequest = OpenGroupAPI.BatchRequest.Child( request = OpenGroupAPI.BatchRequest(
request: Request<TestType, OpenGroupAPI.Endpoint>( requests: [
method: .get, OpenGroupAPI.BatchRequest.Info(
server: "testServer", request: Request<TestType, OpenGroupAPI.Endpoint>(
endpoint: .batch, method: .get,
queryParameters: [:], server: "testServer",
headers: [:], endpoint: .batch,
body: TestType(stringValue: "testValue") queryParameters: [:],
) headers: [:],
body: TestType(stringValue: "testValue")
)
)
]
) )
let subRequestData: Data = try! JSONEncoder().encode(subRequest) let childRequestData: Data = try! JSONEncoder().encode(request.requests[0])
let subRequestString: String? = String(data: subRequestData, encoding: .utf8) let childRequestString: String? = String(data: childRequestData, encoding: .utf8)
expect(subRequestString) expect(childRequestString)
.to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}")) .to(equal("{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}"))
} }
} }
} }
// MARK: - BatchRequestInfo<T, R> // MARK: - BatchRequest.Info
describe("a BatchRequest.Info") { describe("a BatchRequest.Info") {
var request: Request<TestType, OpenGroupAPI.Endpoint>! var request: Request<TestType, OpenGroupAPI.Endpoint>!
@ -143,27 +163,17 @@ class BatchRequestInfoSpec: QuickSpec {
expect(requestInfo.endpoint.path).to(equal(request.endpoint.path)) expect(requestInfo.endpoint.path).to(equal(request.endpoint.path))
expect(requestInfo.responseType == HTTP.BatchSubResponse<TestType>.self).to(beTrue()) expect(requestInfo.responseType == HTTP.BatchSubResponse<TestType>.self).to(beTrue())
} }
}
it("exposes the endpoint correctly") { // MARK: - Convenience
let requestInfo: OpenGroupAPI.BatchRequest.Info = OpenGroupAPI.BatchRequest.Info( // MARK: --Decodable
request: request
)
expect(requestInfo.endpoint.path).to(equal(request.endpoint.path))
}
it("generates a sub request correctly") { describe("a Decodable") {
let batchRequest: OpenGroupAPI.BatchRequest = OpenGroupAPI.BatchRequest( it("decodes correctly") {
requests: [ let jsonData: Data = "{\"stringValue\":\"testValue\"}".data(using: .utf8)!
OpenGroupAPI.BatchRequest.Info( let result: TestType? = try? TestType.decoded(from: jsonData)
request: request
)
]
)
expect(batchRequest.requests[0].method).to(equal(request.method)) expect(result).to(equal(TestType(stringValue: "testValue")))
expect(batchRequest.requests[0].path).to(equal(request.urlPathAndParamsString))
expect(batchRequest.requests[0].headers).to(beNil())
} }
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import PromiseKit import Foundation
import Combine
import GRDB import GRDB
import Sodium import Sodium
import SessionSnodeKit import SessionSnodeKit
@ -779,7 +780,7 @@ class OpenGroupManagerSpec: QuickSpec {
var didComplete: Bool = false // Prevent multi-threading test bugs var didComplete: Bool = false // Prevent multi-threading test bugs
mockStorage mockStorage
.writeAsync { db in .writePublisherFlatMap { db in
openGroupManager openGroupManager
.add( .add(
db, db,
@ -790,8 +791,9 @@ class OpenGroupManagerSpec: QuickSpec {
dependencies: dependencies dependencies: dependencies
) )
} }
.map { _ -> Void in didComplete = true } .subscribe(on: DispatchQueue.main)
.retainUntilComplete() .receiveOnMain(immediately: true)
.sinkUntilComplete(receiveCompletion: { _ in didComplete = true })
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
expect( expect(
@ -810,7 +812,7 @@ class OpenGroupManagerSpec: QuickSpec {
var didComplete: Bool = false // Prevent multi-threading test bugs var didComplete: Bool = false // Prevent multi-threading test bugs
mockStorage mockStorage
.writeAsync { db in .writePublisherFlatMap { db in
openGroupManager openGroupManager
.add( .add(
db, db,
@ -821,8 +823,9 @@ class OpenGroupManagerSpec: QuickSpec {
dependencies: dependencies dependencies: dependencies
) )
} }
.map { _ -> Void in didComplete = true } .subscribe(on: DispatchQueue.main)
.retainUntilComplete() .receiveOnMain(immediately: true)
.sinkUntilComplete(receiveCompletion: { _ in didComplete = true })
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
expect(mockOGMCache) expect(mockOGMCache)
@ -847,7 +850,7 @@ class OpenGroupManagerSpec: QuickSpec {
var didComplete: Bool = false // Prevent multi-threading test bugs var didComplete: Bool = false // Prevent multi-threading test bugs
mockStorage mockStorage
.writeAsync { db in .writePublisherFlatMap { db in
openGroupManager openGroupManager
.add( .add(
db, db,
@ -860,8 +863,9 @@ class OpenGroupManagerSpec: QuickSpec {
dependencies: dependencies dependencies: dependencies
) )
} }
.map { _ -> Void in didComplete = true } .subscribe(on: DispatchQueue.main)
.retainUntilComplete() .receiveOnMain(immediately: true)
.sinkUntilComplete(receiveCompletion: { _ in didComplete = true })
expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50)) expect(didComplete).toEventually(beTrue(), timeout: .milliseconds(50))
expect( expect(
@ -901,7 +905,7 @@ class OpenGroupManagerSpec: QuickSpec {
var error: Error? var error: Error?
mockStorage mockStorage
.writeAsync { db in .writePublisherFlatMap { db in
openGroupManager openGroupManager
.add( .add(
db, db,
@ -912,8 +916,10 @@ class OpenGroupManagerSpec: QuickSpec {
dependencies: dependencies dependencies: dependencies
) )
} }
.catch { error = $0 } .subscribe(on: DispatchQueue.main)
.retainUntilComplete() .receiveOnMain(immediately: true)
.mapError { error.setting(to: $0) }
.sinkUntilComplete()
expect(error?.localizedDescription) expect(error?.localizedDescription)
.toEventually( .toEventually(
@ -1242,8 +1248,10 @@ class OpenGroupManagerSpec: QuickSpec {
).insert(db) ).insert(db)
} }
mockOGMCache.when { $0.groupImagePromises } mockOGMCache.when { $0.groupImagePublishers }
.thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise.value(Data())]) .thenReturn([
OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Just(Data()).setFailureType(to: Error.self).eraseToAnyPublisher()
])
mockStorage.write { db in mockStorage.write { db in
try OpenGroupManager.handlePollInfo( try OpenGroupManager.handlePollInfo(
@ -1679,8 +1687,10 @@ class OpenGroupManagerSpec: QuickSpec {
.updateAll(db, OpenGroup.Columns.imageData.set(to: nil)) .updateAll(db, OpenGroup.Columns.imageData.set(to: nil))
} }
mockOGMCache.when { $0.groupImagePromises } mockOGMCache.when { $0.groupImagePublishers }
.thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise.value(imageData)]) .thenReturn([
OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Just(imageData).setFailureType(to: Error.self).eraseToAnyPublisher()
])
} }
it("uses the provided room image id if available") { it("uses the provided room image id if available") {
@ -1952,8 +1962,10 @@ class OpenGroupManagerSpec: QuickSpec {
it("does nothing if it fails to retrieve the room image") { it("does nothing if it fails to retrieve the room image") {
var didComplete: Bool = false // Prevent multi-threading test bugs var didComplete: Bool = false // Prevent multi-threading test bugs
mockOGMCache.when { $0.groupImagePromises } mockOGMCache.when { $0.groupImagePublishers }
.thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Promise(error: HTTPError.generic)]) .thenReturn([
OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): Fail(error: HTTPError.generic).eraseToAnyPublisher()
])
testPollInfo = OpenGroupAPI.RoomPollInfo( testPollInfo = OpenGroupAPI.RoomPollInfo(
token: "testRoom", token: "testRoom",
@ -3248,11 +3260,11 @@ class OpenGroupManagerSpec: QuickSpec {
} }
it("returns the cached promise if there is one") { it("returns the cached promise if there is one") {
let (promise, _) = Promise<[OpenGroupAPI.Room]>.pending() let publisher = Future { _ in }.eraseToAnyPublisher()
mockOGMCache.when { $0.defaultRoomsPromise }.thenReturn(promise) mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher)
expect(OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies)) expect(OpenGroupManager.getDefaultRoomsIfNeeded(using: dependencies))
.to(equal(promise)) .to(equal(publisher))
} }
it("stores the open group information") { it("stores the open group information") {
@ -3494,12 +3506,12 @@ class OpenGroupManagerSpec: QuickSpec {
} }
it("retrieves the image retrieval promise from the cache if it exists") { it("retrieves the image retrieval promise from the cache if it exists") {
let (promise, _) = Promise<Data>.pending() let publisher = Future<Data, Error> { _ in }.eraseToAnyPublisher()
mockOGMCache mockOGMCache
.when { $0.groupImagePromises } .when { $0.groupImagePublishers }
.thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): promise]) .thenReturn([OpenGroup.idFor(roomToken: "testRoom", server: "testServer"): publisher])
let promise2 = mockStorage.read { db in let publisher2 = mockStorage.read { db in
OpenGroupManager OpenGroupManager
.roomImage( .roomImage(
db, db,
@ -3509,7 +3521,7 @@ class OpenGroupManagerSpec: QuickSpec {
using: dependencies using: dependencies
) )
} }
expect(promise2).to(equal(promise)) expect(publisher2).to(equal(publisher))
} }
it("does not save the fetched image to storage") { it("does not save the fetched image to storage") {

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,19 +1,19 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit import Combine
import SessionUtilitiesKit import SessionUtilitiesKit
@testable import SessionMessagingKit @testable import SessionMessagingKit
class MockOGMCache: Mock<OGMCacheType>, OGMCacheType { class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
var defaultRoomsPromise: Promise<[OpenGroupAPI.Room]>? { var defaultRoomsPublisher: AnyPublisher<[OpenGroupAPI.Room], Error>? {
get { return accept() as? Promise<[OpenGroupAPI.Room]> } get { return accept() as? AnyPublisher<[OpenGroupAPI.Room], Error> }
set { accept(args: [newValue]) } set { accept(args: [newValue]) }
} }
var groupImagePromises: [String: Promise<Data>] { var groupImagePublishers: [String: AnyPublisher<Data, Error>] {
get { return accept() as! [String: Promise<Data>] } get { return accept() as! [String: AnyPublisher<Data, Error>] }
set { accept(args: [newValue]) } set { accept(args: [newValue]) }
} }

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,7 +1,6 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit
import Sodium import Sodium
@testable import SessionMessagingKit @testable import SessionMessagingKit

@ -1,7 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit import Combine
import SessionSnodeKit import SessionSnodeKit
import SessionUtilitiesKit import SessionUtilitiesKit
@ -38,41 +38,45 @@ class TestOnionRequestAPI: OnionRequestAPIType {
class var mockResponse: Data? { return nil } class var mockResponse: Data? { return nil }
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> Promise<(ResponseInfoType, Data?)> { static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
let responseInfo: ResponseInfo = ResponseInfo( let responseInfo: ResponseInfo = ResponseInfo(
requestData: RequestData( requestData: RequestData(
urlString: request.url?.absoluteString, urlString: request.url?.absoluteString,
httpMethod: (request.httpMethod ?? "GET"), httpMethod: (request.httpMethod ?? "GET"),
headers: (request.allHTTPHeaderFields ?? [:]), headers: (request.allHTTPHeaderFields ?? [:]),
body: request.httpBody, body: request.httpBody,
destination: OnionRequestAPIDestination.server( server: server,
host: request.url!.host!, version: .v4,
target: OnionRequestAPIVersion.v4.rawValue, publicKey: x25519PublicKey
x25519PublicKey: x25519PublicKey,
scheme: request.url!.scheme,
port: request.url!.port.map { UInt16($0) }
)
), ),
code: 200, code: 200,
headers: [:] headers: [:]
) )
return Promise.value((responseInfo, mockResponse)) return Just((responseInfo, mockResponse))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
} }
static func sendOnionRequest(_ payload: Data, to snode: Snode) -> Promise<(ResponseInfoType, Data?)> { static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
let responseInfo: ResponseInfo = ResponseInfo( let responseInfo: ResponseInfo = ResponseInfo(
requestData: RequestData( requestData: RequestData(
urlString: "\(snode.address):\(snode.port)/onion_req/v2", urlString: nil,
httpMethod: "POST", httpMethod: "POST",
headers: [:], headers: [:],
snodeMethod: nil,
body: payload, body: payload,
destination: OnionRequestAPIDestination.snode(snode)
server: "",
version: .v3,
publicKey: snode.x25519PublicKey
), ),
code: 200, code: 200,
headers: [:] headers: [:]
) )
return Promise.value((responseInfo, mockResponse)) return Just((responseInfo, mockResponse))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
} }
} }

@ -48,7 +48,6 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
AppReadiness.runNowOrWhenAppDidBecomeReady { AppReadiness.runNowOrWhenAppDidBecomeReady {
let openGroupPollingPublishers: [AnyPublisher<Void, Error>] = self.pollForOpenGroups() let openGroupPollingPublishers: [AnyPublisher<Void, Error>] = self.pollForOpenGroups()
defer { defer {
// TODO: Test this
Publishers Publishers
.MergeMany(openGroupPollingPublishers) .MergeMany(openGroupPollingPublishers)
.sinkUntilComplete( .sinkUntilComplete(

@ -14,30 +14,24 @@ public protocol OnionRequestAPIType {
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
public enum OnionRequestAPI: OnionRequestAPIType { public enum OnionRequestAPI: OnionRequestAPIType {
private static var buildPathsPublisher: Atomic<AnyPublisher<[[Snode]], Error>?> = Atomic(nil) private static var buildPathsPublisher: Atomic<AnyPublisher<[[Snode]], Error>?> = Atomic(nil)
private static var pathFailureCount: Atomic<[[Snode]: UInt]> = Atomic([:])
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. private static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:])
private static var pathFailureCount: [[Snode]: UInt] = [:] public static var guardSnodes: Atomic<Set<Snode>> = Atomic([])
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
private static var snodeFailureCount: [Snode: UInt] = [:]
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
public static var guardSnodes: Set<Snode> = []
// Not a set to ensure we consistently show the same path to the user // Not a set to ensure we consistently show the same path to the user
private static var _paths: [[Snode]]? private static var _paths: Atomic<[[Snode]]?> = Atomic(nil)
public static var paths: [[Snode]] { public static var paths: [[Snode]] {
get { get {
if let paths: [[Snode]] = _paths { return paths } if let paths: [[Snode]] = _paths.wrappedValue { return paths }
let results: [[Snode]]? = Storage.shared.read { db in let results: [[Snode]]? = Storage.shared.read { db in
try? Snode.fetchAllOnionRequestPaths(db) try? Snode.fetchAllOnionRequestPaths(db)
} }
if results?.isEmpty == false { _paths = results } if results?.isEmpty == false { _paths.mutate { $0 = results } }
return (results ?? []) return (results ?? [])
} }
set { _paths = newValue } set { _paths.mutate { $0 = newValue } }
} }
// MARK: - Settings // MARK: - Settings
@ -94,8 +88,8 @@ public enum OnionRequestAPI: OnionRequestAPIType {
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with
/// `Error.insufficientSnodes` if not enough (reliable) snodes are available. /// `Error.insufficientSnodes` if not enough (reliable) snodes are available.
private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> AnyPublisher<Set<Snode>, Error> { private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> AnyPublisher<Set<Snode>, Error> {
guard guardSnodes.count < targetGuardSnodeCount else { guard guardSnodes.wrappedValue.count < targetGuardSnodeCount else {
return Just(guardSnodes) return Just(guardSnodes.wrappedValue)
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
@ -141,7 +135,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
.map { output in Set(output) } .map { output in Set(output) }
.handleEvents( .handleEvents(
receiveOutput: { output in receiveOutput: { output in
OnionRequestAPI.guardSnodes = output OnionRequestAPI.guardSnodes.mutate { $0 = output }
} }
) )
.eraseToAnyPublisher() .eraseToAnyPublisher()
@ -222,10 +216,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
var cancellable: [AnyCancellable] = [] var cancellable: [AnyCancellable] = []
if !paths.isEmpty { if !paths.isEmpty {
guardSnodes.formUnion([ paths[0][0] ]) guardSnodes.mutate {
$0.formUnion([ paths[0][0] ])
if paths.count >= 2 { if paths.count >= 2 {
guardSnodes.formUnion([ paths[1][0] ]) $0.formUnion([ paths[1][0] ])
}
} }
} }
@ -309,20 +305,14 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
private static func dropGuardSnode(_ snode: Snode) { private static func dropGuardSnode(_ snode: Snode) {
#if DEBUG guardSnodes.mutate { snodes in snodes = snodes.filter { $0 != snode } }
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
guardSnodes = guardSnodes.filter { $0 != snode }
} }
private static func drop(_ snode: Snode) throws { private static func drop(_ snode: Snode) throws {
#if DEBUG
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
// We repair the path here because we can do it sync. In the case where we drop a whole // We repair the path here because we can do it sync. In the case where we drop a whole
// path we leave the re-building up to getPath(excluding:) because re-building the path // path we leave the re-building up to getPath(excluding:) because re-building the path
// in that case is async. // in that case is async.
OnionRequestAPI.snodeFailureCount[snode] = 0 OnionRequestAPI.snodeFailureCount.mutate { $0[snode] = 0 }
var oldPaths = paths var oldPaths = paths
guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return } guard let pathIndex = oldPaths.firstIndex(where: { $0.contains(snode) }) else { return }
var path = oldPaths[pathIndex] var path = oldPaths[pathIndex]
@ -344,10 +334,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
private static func drop(_ path: [Snode]) { private static func drop(_ path: [Snode]) {
#if DEBUG OnionRequestAPI.pathFailureCount.mutate { $0[path] = 0 }
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
#endif
OnionRequestAPI.pathFailureCount[path] = 0
var paths = OnionRequestAPI.paths var paths = OnionRequestAPI.paths
guard let pathIndex = paths.firstIndex(of: path) else { return } guard let pathIndex = paths.firstIndex(of: path) else { return }
paths.remove(at: pathIndex) paths.remove(at: pathIndex)
@ -533,7 +520,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
func handleUnspecificError() { func handleUnspecificError() {
guard let path = path else { return } guard let path = path else { return }
var pathFailureCount = OnionRequestAPI.pathFailureCount[path] ?? 0 var pathFailureCount: UInt = (OnionRequestAPI.pathFailureCount.wrappedValue[path] ?? 0)
pathFailureCount += 1 pathFailureCount += 1
if pathFailureCount >= pathFailureThreshold { if pathFailureCount >= pathFailureThreshold {
@ -545,7 +532,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
drop(path) drop(path)
} }
else { else {
OnionRequestAPI.pathFailureCount[path] = pathFailureCount OnionRequestAPI.pathFailureCount.mutate { $0[path] = pathFailureCount }
} }
} }
@ -566,7 +553,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..<message.endIndex] let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..<message.endIndex]
if let path = path, let snode = path.first(where: { $0.ed25519PublicKey == ed25519PublicKey }) { if let path = path, let snode = path.first(where: { $0.ed25519PublicKey == ed25519PublicKey }) {
var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0 var snodeFailureCount: UInt = (OnionRequestAPI.snodeFailureCount.wrappedValue[snode] ?? 0)
snodeFailureCount += 1 snodeFailureCount += 1
if snodeFailureCount >= snodeFailureThreshold { if snodeFailureCount >= snodeFailureThreshold {
@ -579,7 +566,8 @@ public enum OnionRequestAPI: OnionRequestAPIType {
} }
} }
else { else {
OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount OnionRequestAPI.snodeFailureCount
.mutate { $0[snode] = snodeFailureCount }
} }
} else { } else {
// Do nothing // Do nothing

@ -209,10 +209,6 @@ class ThreadSettingsViewModelSpec: QuickSpec {
beforeEach { beforeEach {
viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.rightNavItems.firstValue()??.first?.action?()
viewModel.textChanged("TestNew", for: .nickname) viewModel.textChanged("TestNew", for: .nickname)
// TODO: Enter edit mode by pressing on the first item
// viewModel.tableData.first?
// .elements.first?
// .onTap?()
} }
it("enters the editing state") { it("enters the editing state") {
@ -337,12 +333,7 @@ class ThreadSettingsViewModelSpec: QuickSpec {
context("when entering edit mode") { context("when entering edit mode") {
beforeEach { beforeEach {
viewModel.rightNavItems.firstValue()??.first?.action?() viewModel.rightNavItems.firstValue()??.first?.action?()
viewModel.textChanged("TestUserNew", for: .nickname) viewModel.textChanged("TestNew", for: .nickname)
// TODO: Enter edit mode by pressing on the first item
// viewModel.tableData.first?
// .elements.first?
// .onTap?()
} }
it("enters the editing state") { it("enters the editing state") {

@ -20,6 +20,11 @@ extension Optional {
public func defaulting(to value: Wrapped) -> Wrapped { public func defaulting(to value: Wrapped) -> Wrapped {
return (self ?? value) return (self ?? value)
} }
public mutating func setting(to value: Wrapped) -> Wrapped {
self = value
return value
}
} }
extension Optional where Wrapped == String { extension Optional where Wrapped == String {

@ -1,18 +1,17 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import PromiseKit import Combine
import Quick import Quick
import Nimble import Nimble
@testable import SessionUtilitiesKit @testable import SessionUtilitiesKit
class BatchRequestInfoSpec: QuickSpec { class BatchResponseSpec: QuickSpec {
struct TestType: Codable, Equatable { struct TestType: Codable, Equatable {
let stringValue: String let stringValue: String
} }
struct TestType2: Codable, Equatable { struct TestType2: Codable, Equatable {
let intValue: Int let intValue: Int
let stringValue2: String let stringValue2: String
@ -136,9 +135,9 @@ class BatchRequestInfoSpec: QuickSpec {
} }
} }
// MARK: - --Promise // MARK: - --Combine
describe("a (ResponseInfoType, Data?) Promise") { describe("a (ResponseInfoType, Data?) Publisher") {
var responseInfo: ResponseInfoType! var responseInfo: ResponseInfoType!
var testType: TestType! var testType: TestType!
var testType2: TestType2! var testType2: TestType2!
@ -146,8 +145,8 @@ class BatchRequestInfoSpec: QuickSpec {
beforeEach { beforeEach {
responseInfo = HTTP.ResponseInfo(code: 200, headers: [:]) responseInfo = HTTP.ResponseInfo(code: 200, headers: [:])
testType = TestType(stringValue: "Test") testType = TestType(stringValue: "test1")
testType2 = TestType2(intValue: 1, stringValue2: "Test2") testType2 = TestType2(intValue: 123, stringValue2: "test2")
data = """ data = """
[\([ [\([
try! JSONEncoder().encode( try! JSONEncoder().encode(
@ -173,46 +172,79 @@ class BatchRequestInfoSpec: QuickSpec {
} }
it("decodes valid data correctly") { it("decodes valid data correctly") {
let result = Promise.value((responseInfo, data)) var result: HTTP.BatchResponse?
Just((responseInfo, data))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
.decoded(as: [ .decoded(as: [
HTTP.BatchSubResponse<TestType>.self, HTTP.BatchSubResponse<TestType>.self,
HTTP.BatchSubResponse<TestType2>.self HTTP.BatchSubResponse<TestType2>.self
]) ])
.sinkUntilComplete(
receiveValue: { result = $0 }
)
expect(result.value).toNot(beNil()) expect(result).toNot(beNil())
expect((result.value?[0].1 as? HTTP.BatchSubResponse<TestType>)?.body) expect((result?[0].1 as? HTTP.BatchSubResponse<TestType>)?.body)
.to(equal(testType)) .to(equal(testType))
expect((result.value?[1].1 as? HTTP.BatchSubResponse<TestType2>)?.body) expect((result?[1].1 as? HTTP.BatchSubResponse<TestType2>)?.body)
.to(equal(testType2)) .to(equal(testType2))
} }
it("fails if there is no data") { it("fails if there is no data") {
let result = Promise.value((responseInfo, nil)).decoded(as: []) var error: Error?
Just((responseInfo, nil))
expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) .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") { it("fails if the data is not JSON") {
let result = Promise.value((responseInfo, Data([1, 2, 3]))).decoded(as: []) var error: Error?
Just((responseInfo, Data([1, 2, 3])))
expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) .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") { it("fails if the data is not a JSON array") {
let result = Promise.value((responseInfo, "{}".data(using: .utf8))).decoded(as: []) var error: Error?
Just((responseInfo, "{}".data(using: .utf8)))
expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) .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") { it("fails if the JSON array does not have the same number of items as the expected types") {
let result = Promise.value((responseInfo, data)) var error: Error?
Just((responseInfo, data))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
.decoded(as: [ .decoded(as: [
HTTP.BatchSubResponse<TestType>.self, HTTP.BatchSubResponse<TestType>.self,
HTTP.BatchSubResponse<TestType2>.self, HTTP.BatchSubResponse<TestType2>.self,
HTTP.BatchSubResponse<TestType2>.self HTTP.BatchSubResponse<TestType2>.self
]) ])
.mapError { error.setting(to: $0) }
.sinkUntilComplete()
expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) expect(error?.localizedDescription)
.to(equal(HTTPError.parsingFailed.localizedDescription))
} }
it("fails if one of the JSON array values fails to decode") { it("fails if one of the JSON array values fails to decode") {
@ -230,13 +262,20 @@ class BatchRequestInfoSpec: QuickSpec {
.map { String(data: $0, encoding: .utf8)! } .map { String(data: $0, encoding: .utf8)! }
.joined(separator: ",")),{"test": "test"}] .joined(separator: ",")),{"test": "test"}]
""".data(using: .utf8)! """.data(using: .utf8)!
let result = Promise.value((responseInfo, data))
var error: Error?
Just((responseInfo, data))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
.decoded(as: [ .decoded(as: [
HTTP.BatchSubResponse<TestType>.self, HTTP.BatchSubResponse<TestType>.self,
HTTP.BatchSubResponse<TestType2>.self HTTP.BatchSubResponse<TestType2>.self
]) ])
.mapError { error.setting(to: $0) }
.sinkUntilComplete()
expect(result.error?.localizedDescription).to(equal(HTTPError.parsingFailed.localizedDescription)) expect(error?.localizedDescription)
.to(equal(HTTPError.parsingFailed.localizedDescription))
} }
} }
} }

@ -1,6 +1,7 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import SessionUtilitiesKit
import Quick import Quick
import Nimble import Nimble

@ -1,28 +1,19 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation import Foundation
import Combine
import GRDB import GRDB
import PromiseKit
import SessionUtilitiesKit import SessionUtilitiesKit
class SynchronousStorage: Storage { class SynchronousStorage: Storage {
override func writeAsync<T>(updates: @escaping (Database) throws -> T) { override func writePublisher<T>(updates: @escaping (Database) throws -> T) -> AnyPublisher<T, Error> {
super.write(updates: updates) guard let result: T = super.write(updates: updates) else {
} return Fail(error: StorageError.generic)
.eraseToAnyPublisher()
override func writeAsync<T>(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Swift.Result<T, Error>) throws -> Void) {
super.write { db in
do {
var result: T?
try db.inTransaction {
result = try updates(db)
return .commit
}
try? completion(db, .success(result!))
}
catch {
try? completion(db, .failure(error))
}
} }
return Just(result)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
} }
} }

Loading…
Cancel
Save