Default community logic refactoring and bug fixes

• Moved the default community retrieval logic to be in it's job
• Fixed a bug where a parsing failure could be incorrectly reported as a successful request
pull/894/head
Morgan Pretty 6 months ago
parent dedff539f2
commit a5c565cacb

@ -680,6 +680,7 @@
FD481A9A2CB4CAE500ECC4CF /* CommonSMKMockExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F562CAA28CF00C0B51B /* CommonSMKMockExtensions.swift */; };
FD481A9B2CB4CAF100ECC4CF /* CustomArgSummaryDescribable+SMK.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD336F572CAA28CF00C0B51B /* CustomArgSummaryDescribable+SMK.swift */; };
FD481A9C2CB4D58300ECC4CF /* MockSnodeAPICache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3765DE2AD8F03100DC1489 /* MockSnodeAPICache.swift */; };
FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD481AA22CB889A400ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift */; };
FD49E2462B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; };
FD49E2472B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; };
FD49E2482B05C1D500FFBBB5 /* MockKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */; };
@ -1885,6 +1886,7 @@
FD481A8F2CAD16EA00ECC4CF /* LibSessionGroupInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionGroupInfoSpec.swift; sourceTree = "<group>"; };
FD481A912CAD17D900ECC4CF /* LibSessionGroupMembersSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionGroupMembersSpec.swift; sourceTree = "<group>"; };
FD481A932CAE0ADD00ECC4CF /* MockAppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppContext.swift; sourceTree = "<group>"; };
FD481AA22CB889A400ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveDefaultOpenGroupRoomsJobSpec.swift; sourceTree = "<group>"; };
FD49E2452B05C1D500FFBBB5 /* MockKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeychain.swift; sourceTree = "<group>"; };
FD4B200D283492210034334B /* InsetLockableTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetLockableTableView.swift; sourceTree = "<group>"; };
FD4C4E9B2B02E2A300C72199 /* DisplayPictureError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayPictureError.swift; sourceTree = "<group>"; };
@ -4357,8 +4359,9 @@
FD96F3A229DBC3BA00401309 /* Jobs */ = {
isa = PBXGroup;
children = (
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */,
FD3FAB662AF0C47000DC5421 /* DisplayPictureDownloadJobSpec.swift */,
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */,
FD481AA22CB889A400ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift */,
);
path = Jobs;
sourceTree = "<group>";
@ -6696,6 +6699,7 @@
FD72BDA42BE3690B00CF6CF6 /* CryptoSMKSpec.swift in Sources */,
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
FD336F6C2CAA29C600C0B51B /* CommunityPollerSpec.swift in Sources */,
FD481AA32CB889AE00ECC4CF /* RetrieveDefaultOpenGroupRoomsJobSpec.swift in Sources */,
FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */,
FD01502A2CA23DB7005B08A1 /* GRDBExtensions.swift in Sources */,
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,

@ -158,7 +158,7 @@ final class OpenGroupSuggestionGrid: UIView, UICollectionViewDataSource, UIColle
heightConstraint = set(.height, to: OpenGroupSuggestionGrid.cellHeight)
widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
dependencies[singleton: .openGroupManager].getDefaultRoomsIfNeeded()
dependencies[cache: .openGroupManager].defaultRoomsPublisher
.subscribe(on: DispatchQueue.global(qos: .default))
.receive(on: DispatchQueue.main)
.sinkUntilComplete(

@ -2,6 +2,7 @@
import Foundation
import GRDB
import SessionSnodeKit
import SessionUtilitiesKit
// MARK: - Log.Category
@ -25,10 +26,18 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
deferred: @escaping (Job) -> Void,
using dependencies: Dependencies
) {
// Don't run when inactive or not in main app
guard dependencies[defaults: .appGroup, key: .isMainAppActive] else {
return deferred(job) // Don't need to do anything if it's not the main app
}
/// Don't run when inactive or not in main app
///
/// Additionally, since this job can be triggered by the user viewing the "Join Community" screen it's possible for multiple jobs to run at
/// the same time, we don't want to waste bandwidth by making redundant calls to fetch the default rooms so don't do anything if there
/// is already a job running
guard
dependencies[defaults: .appGroup, key: .isMainAppActive],
dependencies[singleton: .jobRunner]
.jobInfoFor(state: .running, variant: .retrieveDefaultOpenGroupRooms)
.filter({ key, info in key != job.id }) // Exclude this job
.isEmpty
else { return deferred(job) }
// The OpenGroupAPI won't make any API calls if there is no entry for an OpenGroup
// in the database so we need to create a dummy one to retrieve the default room data
@ -49,10 +58,19 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
.upserted(db)
}
dependencies[singleton: .openGroupManager]
.getDefaultRoomsIfNeeded()
.subscribe(on: queue)
.receive(on: queue)
/// Try to retrieve the default rooms 8 times
dependencies[singleton: .storage]
.readPublisher { [dependencies] db -> Network.PreparedRequest<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
try OpenGroupAPI.preparedCapabilitiesAndRooms(
db,
on: OpenGroupAPI.defaultServer,
using: dependencies
)
}
.flatMap { [dependencies] request in request.send(using: dependencies) }
.subscribe(on: queue, using: dependencies)
.receive(on: queue, using: dependencies)
.retry(8, using: dependencies)
.sinkUntilComplete(
receiveCompletion: { result in
switch result {
@ -64,7 +82,102 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
Log.error(.cat, "Failed to get default Community rooms due to error: \(error)")
failure(job, error, false)
}
},
receiveValue: { info, response in
let defaultRooms: [OpenGroupManager.DefaultRoomInfo]? = dependencies[singleton: .storage].write { db -> [OpenGroupManager.DefaultRoomInfo] in
// Store the capabilities first
OpenGroupManager.handleCapabilities(
db,
capabilities: response.capabilities.data,
on: OpenGroupAPI.defaultServer
)
let existingImageIds: [String: String] = try OpenGroup
.filter(OpenGroup.Columns.server == OpenGroupAPI.defaultServer)
.filter(OpenGroup.Columns.imageId != nil)
.fetchAll(db)
.reduce(into: [:]) { result, next in result[next.id] = next.imageId }
let result: [OpenGroupManager.DefaultRoomInfo] = try response.rooms.data
.compactMap { room -> OpenGroupManager.DefaultRoomInfo? in
/// Try to insert an inactive version of the OpenGroup (use `insert` rather than
/// `save` as we want it to fail if the room already exists)
do {
return (
room,
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: room.token,
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: room.name,
roomDescription: room.roomDescription,
imageId: room.imageId,
userCount: room.activeUsers,
infoUpdates: room.infoUpdates
)
.inserted(db)
)
}
catch {
return try OpenGroup
.fetchOne(
db,
id: OpenGroup.idFor(
roomToken: room.token,
server: OpenGroupAPI.defaultServer
)
)
.map { (room, $0) }
}
}
/// Schedule the room image download (if it doesn't match out current one)
result.forEach { room, openGroup in
let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer)
guard
let imageId: String = room.imageId,
imageId != existingImageIds[openGroupId] ||
openGroup.displayPictureFilename == nil
else { return }
dependencies[singleton: .jobRunner].add(
db,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: imageId,
roomToken: room.token,
server: OpenGroupAPI.defaultServer
),
timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000)
)
),
canStartJob: true
)
}
return result
}
/// Update the `openGroupManager` cache to have the default rooms
dependencies.mutate(cache: .openGroupManager) { cache in
cache.setDefaultRoomInfo(defaultRooms ?? [])
}
}
)
}
public static func run(using dependencies: Dependencies) {
RetrieveDefaultOpenGroupRoomsJob.run(
Job(variant: .retrieveDefaultOpenGroupRooms, behaviour: .runOnce),
queue: DispatchQueue.global(qos: .default),
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
}
}

@ -386,12 +386,14 @@ public enum OpenGroupAPI {
let capabilitiesInfo: ResponseInfoType = maybeCapabilities,
let capabilities: Capabilities = maybeCapabilities?.body,
let roomsInfo: ResponseInfoType = maybeRooms,
let rooms: [Room] = maybeRooms?.body
let roomsResponse: Network.BatchSubResponse<[Room]> = maybeRooms,
!roomsResponse.failedToParseBody
else { throw NetworkError.parsingFailed }
// We might want to remove all default rooms for some reason so support that case
return (
capabilities: (info: capabilitiesInfo, data: capabilities),
rooms: (info: roomsInfo, data: rooms)
rooms: (info: roomsInfo, data: (roomsResponse.body ?? []))
)
}
}

@ -20,7 +20,7 @@ public extension Singleton {
public extension Cache {
static let openGroupManager: CacheConfig<OGMCacheType, OGMImmutableCacheType> = Dependencies.create(
identifier: "openGroupManager",
createInstance: { _ in OpenGroupManager.Cache() },
createInstance: { dependencies in OpenGroupManager.Cache(using: dependencies) },
mutableInstance: { $0 },
immutableInstance: { $0 }
)
@ -925,137 +925,40 @@ public final class OpenGroupManager {
case .group: return false
}
}
}
@discardableResult public func getDefaultRoomsIfNeeded() -> AnyPublisher<[DefaultRoomInfo], Error> {
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies[cache: .openGroupManager].defaultRoomsPublisher {
return existingPublisher
}
// Try to retrieve the default rooms 8 times
let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies[singleton: .storage]
.readPublisher { [dependencies] db -> Network.PreparedRequest<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
try OpenGroupAPI.preparedCapabilitiesAndRooms(
db,
on: OpenGroupAPI.defaultServer,
using: dependencies
)
}
.flatMap { [dependencies] request in request.send(using: dependencies) }
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
.retry(8, using: dependencies)
.map { [dependencies] info, response -> [DefaultRoomInfo]? in
dependencies[singleton: .storage].write { db -> [DefaultRoomInfo] in
// Store the capabilities first
OpenGroupManager.handleCapabilities(
db,
capabilities: response.capabilities.data,
on: OpenGroupAPI.defaultServer
)
let existingImageIds: [String: String] = try OpenGroup
.filter(OpenGroup.Columns.server == OpenGroupAPI.defaultServer)
.filter(OpenGroup.Columns.imageId != nil)
.fetchAll(db)
.reduce(into: [:]) { result, next in result[next.id] = next.imageId }
let result: [DefaultRoomInfo] = try response.rooms.data
.compactMap { room -> DefaultRoomInfo? in
// Try to insert an inactive version of the OpenGroup (use 'insert'
// rather than 'save' as we want it to fail if the room already exists)
do {
return (
room,
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: room.token,
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: room.name,
roomDescription: room.roomDescription,
imageId: room.imageId,
userCount: room.activeUsers,
infoUpdates: room.infoUpdates
)
.inserted(db)
)
}
catch {
return try OpenGroup
.fetchOne(
db,
id: OpenGroup.idFor(
roomToken: room.token,
server: OpenGroupAPI.defaultServer
)
)
.map { (room, $0) }
}
}
/// Schedule the room image download (if it doesn't match out current one)
result.forEach { room, _ in
let openGroupId: String = OpenGroup.idFor(roomToken: room.token, server: OpenGroupAPI.defaultServer)
guard
let imageId: String = room.imageId,
imageId != existingImageIds[openGroupId]
else { return }
// MARK: - OpenGroupManager Cache
dependencies[singleton: .jobRunner].add(
db,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: imageId,
roomToken: room.token,
server: OpenGroupAPI.defaultServer
),
timestamp: (dependencies[cache: .snodeAPI].currentOffsetTimestampMs() / 1000)
)
),
canStartJob: true
)
}
public extension OpenGroupManager {
class Cache: OGMCacheType {
private let dependencies: Dependencies
private let defaultRoomsSubject: CurrentValueSubject<[DefaultRoomInfo], Error> = CurrentValueSubject([])
private var _timeSinceLastOpen: TimeInterval?
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
return result
}
}
.map { ($0 ?? []) }
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error> {
defaultRoomsSubject
.handleEvents(
receiveCompletion: { [dependencies] result in
switch result {
case .finished: break
case .failure:
dependencies.mutate(cache: .openGroupManager) { cache in
cache.defaultRoomsPublisher = nil
}
receiveSubscription: { [weak defaultRoomsSubject, dependencies] _ in
/// If we don't have any default rooms in memory then we haven't fetched this launch so schedule
/// the `RetrieveDefaultOpenGroupRoomsJob` if one isn't already running
if defaultRoomsSubject?.value.isEmpty == true {
RetrieveDefaultOpenGroupRoomsJob.run(using: dependencies)
}
}
)
.shareReplay(1)
.filter { !$0.isEmpty }
.eraseToAnyPublisher()
dependencies.mutate(cache: .openGroupManager) { cache in
cache.defaultRoomsPublisher = publisher
}
// Hold on to the publisher until it has completed at least once
publisher.sinkUntilComplete()
// MARK: - Initialization
return publisher
init(using dependencies: Dependencies) {
self.dependencies = dependencies
}
}
// MARK: - OpenGroupManager Cache
// MARK: - Functions
public extension OpenGroupManager {
class Cache: OGMCacheType {
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
fileprivate var _timeSinceLastOpen: TimeInterval?
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
return storedTimeSinceLastOpen
@ -1070,7 +973,9 @@ public extension OpenGroupManager {
return dependencies.dateNow.timeIntervalSince(lastOpen)
}
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
public func setDefaultRoomInfo(_ info: [DefaultRoomInfo]) {
defaultRoomsSubject.send(info)
}
}
}
@ -1078,15 +983,16 @@ public extension OpenGroupManager {
/// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way
public protocol OGMImmutableCacheType: ImmutableCacheType {
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get }
var pendingChanges: [OpenGroupAPI.PendingChange] { get }
}
public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType {
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set }
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> { get }
var pendingChanges: [OpenGroupAPI.PendingChange] { get set }
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
func setDefaultRoomInfo(_ info: [OpenGroupManager.DefaultRoomInfo])
}

@ -0,0 +1,608 @@
// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Quick
import Nimble
@testable import SessionSnodeKit
@testable import SessionMessagingKit
@testable import SessionUtilitiesKit
class RetrieveDefaultOpenGroupRoomsJobSpec: QuickSpec {
override class func spec() {
// MARK: Configuration
@TestState var dependencies: TestDependencies! = TestDependencies { dependencies in
dependencies.forceSynchronous = true
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
}
@TestState(singleton: .storage, in: dependencies) var mockStorage: Storage! = SynchronousStorage(
customWriter: try! DatabaseQueue(),
migrationTargets: [
SNUtilitiesKit.self,
SNMessagingKit.self
],
using: dependencies,
initialData: { db in
try Identity(variant: .x25519PublicKey, data: Data(hex: TestConstants.publicKey)).insert(db)
try Identity(variant: .x25519PrivateKey, data: Data(hex: TestConstants.privateKey)).insert(db)
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
}
)
@TestState(defaults: .appGroup, in: dependencies) var mockUserDefaults: MockUserDefaults! = MockUserDefaults(
initialSetup: { defaults in
defaults.when { $0.bool(forKey: .any) }.thenReturn(true)
}
)
@TestState(singleton: .network, in: dependencies) var mockNetwork: MockNetwork! = MockNetwork(
initialSetup: { network in
network
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(
MockNetwork.batchResponseData(
with: [
(
OpenGroupAPI.Endpoint.capabilities,
OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse()
),
(
OpenGroupAPI.Endpoint.rooms,
[
OpenGroupAPI.Room.mock.with(
token: "testRoom",
name: "TestRoomName"
),
OpenGroupAPI.Room.mock.with(
token: "testRoom2",
name: "TestRoomName2",
infoUpdates: 12,
imageId: "12"
)
].batchSubResponse()
)
]
)
)
}
)
@TestState(singleton: .jobRunner, in: dependencies) var mockJobRunner: MockJobRunner! = MockJobRunner(
initialSetup: { jobRunner in
jobRunner
.when { $0.add(.any, job: .any, dependantJob: .any, canStartJob: .any) }
.thenReturn(nil)
jobRunner
.when { $0.upsert(.any, job: .any, canStartJob: .any) }
.thenReturn(nil)
jobRunner
.when { $0.jobInfoFor(jobs: .any, state: .any, variant: .any) }
.thenReturn([:])
}
)
@TestState(cache: .openGroupManager, in: dependencies) var mockOGMCache: MockOGMCache! = MockOGMCache(
initialSetup: { cache in
cache.when { $0.setDefaultRoomInfo(.any) }.thenReturn(())
}
)
@TestState var job: Job! = Job(variant: .retrieveDefaultOpenGroupRooms)
@TestState var error: Error? = nil
@TestState var permanentFailure: Bool! = false
@TestState var wasDeferred: Bool! = false
// MARK: - a RetrieveDefaultOpenGroupRoomsJob
describe("a RetrieveDefaultOpenGroupRoomsJob") {
// MARK: -- defers the job if the main app is not running
it("defers the job if the main app is not running") {
mockUserDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(false)
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in wasDeferred = true },
using: dependencies
)
expect(wasDeferred).to(beTrue())
}
// MARK: -- does not defer the job when the main app is running
it("does not defer the job when the main app is running") {
mockUserDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(true)
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in wasDeferred = true },
using: dependencies
)
expect(wasDeferred).to(beFalse())
}
// MARK: -- defers the job if there is an existing job running
it("defers the job if there is an existing job running") {
mockJobRunner
.when { $0.jobInfoFor(jobs: .any, state: .running, variant: .retrieveDefaultOpenGroupRooms) }
.thenReturn([
101: JobRunner.JobInfo(
variant: .retrieveDefaultOpenGroupRooms,
threadId: nil,
interactionId: nil,
detailsData: nil,
uniqueHashValue: nil
)
])
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in wasDeferred = true },
using: dependencies
)
expect(wasDeferred).to(beTrue())
}
// MARK: -- does not defer the job when there is no existing job
it("does not defer the job when there is no existing job") {
mockJobRunner
.when { $0.jobInfoFor(jobs: .any, state: .running, variant: .retrieveDefaultOpenGroupRooms) }
.thenReturn([:])
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in wasDeferred = true },
using: dependencies
)
expect(wasDeferred).to(beFalse())
}
// MARK: -- creates an inactive entry in the database if one does not exist
it("creates an inactive entry in the database if one does not exist") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.errorResponse())
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) }
expect(openGroups?.count).to(equal(1))
expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer]))
expect(openGroups?.map { $0.roomToken }).to(equal([""]))
expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey]))
expect(openGroups?.map { $0.isActive }).to(equal([false]))
expect(openGroups?.map { $0.name }).to(equal([""]))
}
// MARK: -- does not create a new entry if one already exists
it("does not create a new entry if one already exists") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.errorResponse())
mockStorage.write { db in
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestExisting",
userCount: 0,
infoUpdates: 0
)
.insert(db)
}
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) }
expect(openGroups?.count).to(equal(1))
expect(openGroups?.map { $0.server }).to(equal([OpenGroupAPI.defaultServer]))
expect(openGroups?.map { $0.roomToken }).to(equal([""]))
expect(openGroups?.map { $0.publicKey }).to(equal([OpenGroupAPI.defaultServerPublicKey]))
expect(openGroups?.map { $0.isActive }).to(equal([false]))
expect(openGroups?.map { $0.name }).to(equal(["TestExisting"]))
}
// MARK: -- sends the correct request
it("sends the correct request") {
mockStorage.write { db in
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestExisting",
userCount: 0,
infoUpdates: 0
)
.insert(db)
}
let expectedRequest: Network.PreparedRequest<OpenGroupAPI.CapabilitiesAndRoomsResponse>! = mockStorage.read { db in
try OpenGroupAPI.preparedCapabilitiesAndRooms(
db,
on: OpenGroupAPI.defaultServer,
using: dependencies
)
}
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockNetwork)
.to(call { network in
network.send(
expectedRequest.body,
to: expectedRequest.destination,
requestTimeout: expectedRequest.requestTimeout,
requestAndPathBuildTimeout: expectedRequest.requestAndPathBuildTimeout
)
})
}
// MARK: -- will retry 8 times before it fails
it("will retry 8 times before it fails") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.nullResponse())
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, error_, permanentFailure_ in
error = error_
permanentFailure = permanentFailure_
},
deferred: { _ in },
using: dependencies
)
expect(error).to(matchError(NetworkError.parsingFailed))
expect(mockNetwork) // First attempt + 8 retries
.to(call(.exactly(times: 9)) { network in
network.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any)
})
}
// MARK: -- stores the updated capabilities
it("stores the updated capabilities") {
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
let capabilities: [Capability]? = mockStorage.read { db in try Capability.fetchAll(db) }
expect(capabilities?.count).to(equal(2))
expect(capabilities?.map { $0.openGroupServer })
.to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer]))
expect(capabilities?.map { $0.variant }).to(equal([.blind, .reactions]))
expect(capabilities?.map { $0.isMissing }).to(equal([false, false]))
}
// MARK: -- inserts the returned rooms
it("inserts the returned rooms") {
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) }
expect(openGroups?.count).to(equal(3)) // 1 for the entry used to fetch the default rooms
expect(openGroups?.map { $0.server })
.to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer]))
expect(openGroups?.map { $0.roomToken }).to(equal(["", "testRoom", "testRoom2"]))
expect(openGroups?.map { $0.publicKey })
.to(equal([
OpenGroupAPI.defaultServerPublicKey,
OpenGroupAPI.defaultServerPublicKey,
OpenGroupAPI.defaultServerPublicKey
]))
expect(openGroups?.map { $0.isActive }).to(equal([false, false, false]))
expect(openGroups?.map { $0.name }).to(equal(["", "TestRoomName", "TestRoomName2"]))
}
// MARK: -- does not override existing rooms that were returned
it("does not override existing rooms that were returned") {
mockStorage.write { db in
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "testRoom",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestExisting",
userCount: 0,
infoUpdates: 0
)
.insert(db)
}
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(
MockNetwork.batchResponseData(
with: [
(OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()),
(
OpenGroupAPI.Endpoint.rooms,
try! JSONEncoder().with(outputFormatting: .sortedKeys).encode(
Network.BatchSubResponse(
code: 200,
headers: [:],
body: [
OpenGroupAPI.Room.mock.with(
token: "testRoom",
name: "TestReplacementName"
)
],
failedToParseBody: false
)
)
)
]
)
)
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
let openGroups: [OpenGroup]? = mockStorage.read { db in try OpenGroup.fetchAll(db) }
expect(openGroups?.count).to(equal(2)) // 1 for the entry used to fetch the default rooms
expect(openGroups?.map { $0.server })
.to(equal([OpenGroupAPI.defaultServer, OpenGroupAPI.defaultServer]))
expect(openGroups?.map { $0.roomToken }.sorted()).to(equal(["", "testRoom"]))
expect(openGroups?.map { $0.publicKey })
.to(equal([OpenGroupAPI.defaultServerPublicKey, OpenGroupAPI.defaultServerPublicKey]))
expect(openGroups?.map { $0.isActive }).to(equal([false, false]))
expect(openGroups?.map { $0.name }.sorted()).to(equal(["", "TestExisting"]))
}
// MARK: -- schedules a display picture download
it("schedules a display picture download") {
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockJobRunner)
.to(call(matchingParameters: .all) {
$0.add(
.any,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: "12",
roomToken: "testRoom2",
server: OpenGroupAPI.defaultServer
),
timestamp: 1234567890
)
),
dependantJob: nil,
canStartJob: true
)
})
}
// MARK: -- schedules a display picture download if the imageId has changed
it("schedules a display picture download if the imageId has changed") {
mockStorage.write { db in
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "testRoom2",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestExisting",
imageId: "10",
userCount: 0,
infoUpdates: 10,
displayPictureFilename: "TestFilename"
)
.insert(db)
}
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockJobRunner)
.to(call(matchingParameters: .all) {
$0.add(
.any,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: "12",
roomToken: "testRoom2",
server: OpenGroupAPI.defaultServer
),
timestamp: 1234567890
)
),
dependantJob: nil,
canStartJob: true
)
})
}
// MARK: -- does not schedule a display picture download if there is no imageId
it("does not schedule a display picture download if there is no imageId") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(
MockNetwork.batchResponseData(
with: [
(
OpenGroupAPI.Endpoint.capabilities,
OpenGroupAPI.Capabilities(capabilities: [.blind, .reactions]).batchSubResponse()
),
(
OpenGroupAPI.Endpoint.rooms,
[
OpenGroupAPI.Room.mock.with(
token: "testRoom",
name: "TestRoomName"
),
OpenGroupAPI.Room.mock.with(
token: "testRoom2",
name: "TestRoomName2"
)
].batchSubResponse()
)
]
)
)
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockJobRunner)
.toNot(call { $0.add(.any, job: .any, dependantJob: .any, canStartJob: .any) })
}
// MARK: -- does not schedule a display picture download if the imageId matches and the image has already been downloaded
it("does not schedule a display picture download if the imageId matches and the image has already been downloaded") {
mockStorage.write { db in
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "testRoom2",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestExisting",
imageId: "12",
userCount: 0,
infoUpdates: 12,
displayPictureFilename: "TestFilename"
)
.insert(db)
}
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockJobRunner)
.toNot(call { $0.add(.any, job: .any, dependantJob: .any, canStartJob: .any) })
}
// MARK: -- updates the cache with the default rooms
it("does not schedule a display picture download if the imageId matches and the image has already been downloaded") {
RetrieveDefaultOpenGroupRoomsJob.run(
job,
queue: .main,
success: { _, _ in },
failure: { _, _, _ in },
deferred: { _ in },
using: dependencies
)
expect(mockOGMCache)
.toNot(call(matchingParameters: .all) {
$0.setDefaultRoomInfo([
(
room: OpenGroupAPI.Room.mock.with(
token: "testRoom",
name: "TestRoomName"
),
openGroup: OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "testRoom",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestRoomName",
userCount: 0,
infoUpdates: 0
)
),
(
room: OpenGroupAPI.Room.mock.with(
token: "testRoom2",
name: "TestRoomName2",
infoUpdates: 12,
imageId: "12"
),
openGroup: OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "testRoom2",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "TestRoomName2",
imageId: "12",
userCount: 0,
infoUpdates: 12,
displayPictureFilename: nil
)
)
])
})
}
}
}
}

@ -567,13 +567,20 @@ class OpenGroupAPISpec: QuickSpec {
}
// MARK: ---- and given an invalid response
context("and given an invalid response") {
// MARK: ------ errors when not given a room response
it("errors when not given a room response") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(Network.BatchResponse.mockCapabilitiesAndBanResponse)
.thenReturn(
MockNetwork.batchResponseData(with: [
(OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()),
(
OpenGroupAPI.Endpoint.userBan(""),
OpenGroupAPI.DirectMessage.mockBatchSubResponse()
)
])
)
var response: (info: ResponseInfoType, data: OpenGroupAPI.CapabilitiesAndRoomsResponse)?

@ -131,7 +131,13 @@ class OpenGroupManagerSpec: QuickSpec {
.thenReturn([:])
}
)
@TestState(singleton: .network, in: dependencies) var mockNetwork: MockNetwork! = MockNetwork()
@TestState(singleton: .network, in: dependencies) var mockNetwork: MockNetwork! = MockNetwork(
initialSetup: { network in
network
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.errorResponse())
}
)
@TestState(singleton: .crypto, in: dependencies) var mockCrypto: MockCrypto! = MockCrypto(
initialSetup: { crypto in
crypto.when { $0.generate(.hash(message: .any, length: .any)) }.thenReturn([])
@ -170,6 +176,11 @@ class OpenGroupManagerSpec: QuickSpec {
defaults.when { $0.integer(forKey: .any) }.thenReturn(0)
}
)
@TestState(defaults: .appGroup, in: dependencies) var mockAppGroupDefaults: MockUserDefaults! = MockUserDefaults(
initialSetup: { defaults in
defaults.when { $0.bool(forKey: .any) }.thenReturn(false)
}
)
@TestState(cache: .general, in: dependencies) var mockGeneralCache: MockGeneralCache! = MockGeneralCache(
initialSetup: { cache in
cache.when { $0.sessionId }.thenReturn(SessionId(.standard, hex: TestConstants.publicKey))
@ -177,12 +188,10 @@ class OpenGroupManagerSpec: QuickSpec {
)
@TestState(cache: .openGroupManager, in: dependencies) var mockOGMCache: MockOGMCache! = MockOGMCache(
initialSetup: { cache in
cache
.when { $0.defaultRoomsPublisher = .any(type: [OpenGroupManager.DefaultRoomInfo].self) }
.thenReturn(())
cache.when { $0.pendingChanges }.thenReturn([])
cache.when { $0.pendingChanges = .any }.thenReturn(())
cache.when { $0.getTimeSinceLastOpen(using: .any) }.thenReturn(0)
cache.when { $0.setDefaultRoomInfo(.any) }.thenReturn(())
}
)
@TestState var mockPoller: MockCommunityPoller! = MockCommunityPoller(
@ -202,7 +211,7 @@ class OpenGroupManagerSpec: QuickSpec {
)
@TestState var disposables: [AnyCancellable]! = []
@TestState var cache: OpenGroupManager.Cache! = OpenGroupManager.Cache()
@TestState var cache: OpenGroupManager.Cache! = OpenGroupManager.Cache(using: dependencies)
@TestState var openGroupManager: OpenGroupManager! = OpenGroupManager(using: dependencies)
// MARK: - an OpenGroupManager
@ -2738,206 +2747,51 @@ class OpenGroupManagerSpec: QuickSpec {
}
}
// MARK: -- when getting the default rooms if needed
context("when getting the default rooms if needed") {
beforeEach {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(Network.BatchResponse.mockCapabilitiesAndRoomsResponse)
// MARK: -- when accessing the default rooms publisher
context("when accessing the default rooms publisher") {
// MARK: ---- starts a job to retrieve the default rooms if we have none
it("starts a job to retrieve the default rooms if we have none") {
mockAppGroupDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(true)
mockStorage.write { db in
try OpenGroup.deleteAll(db)
// This is done in the 'RetrieveDefaultOpenGroupRoomsJob'
_ = try OpenGroup(
try OpenGroup(
server: OpenGroupAPI.defaultServer,
roomToken: "",
publicKey: OpenGroupAPI.defaultServerPublicKey,
isActive: false,
name: "",
name: "TestExisting",
userCount: 0,
infoUpdates: 0
)
.insert(db)
}
mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(nil)
mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: .any)
}.thenReturn(nil)
mockUserDefaults.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.set(anyAny(), forKey: .any)
}.thenReturn(())
}
// MARK: ---- caches the publisher if there is no cached publisher
it("caches the publisher if there is no cached publisher") {
let publisher = openGroupManager.getDefaultRoomsIfNeeded()
expect(mockOGMCache)
.to(call(matchingParameters: .all) {
$0.defaultRoomsPublisher = publisher
})
}
// MARK: ---- returns the cached publisher if there is one
it("returns the cached publisher if there is one") {
let uniqueRoomInstance: OpenGroupAPI.Room = OpenGroupAPI.Room.mock.with(
token: "UniqueId",
name: ""
)
let group: OpenGroup = OpenGroup(
server: "testServer",
roomToken: "UniqueId",
publicKey: "",
isActive: true,
name: "",
userCount: 0,
infoUpdates: 0
let expectedRequest: Network.PreparedRequest<OpenGroupAPI.CapabilitiesAndRoomsResponse>! = mockStorage.read { db in
try OpenGroupAPI.preparedCapabilitiesAndRooms(
db,
on: OpenGroupAPI.defaultServer,
using: dependencies
)
let publisher = Future<[OpenGroupManager.DefaultRoomInfo], Error> { resolver in
resolver(Result.success([(uniqueRoomInstance, group)]))
}
.shareReplay(1)
.eraseToAnyPublisher()
mockOGMCache.when { $0.defaultRoomsPublisher }.thenReturn(publisher)
let publisher2 = openGroupManager.getDefaultRoomsIfNeeded()
expect(publisher2.firstValue()?.map { $0.room })
.to(equal(publisher.firstValue()?.map { $0.room }))
}
cache.defaultRoomsPublisher.sinkUntilComplete()
// MARK: ---- stores the open group information
it("stores the open group information") {
openGroupManager.getDefaultRoomsIfNeeded()
// 1 for the value returned from the API and 1 for the default added
// by the 'RetrieveDefaultOpenGroupRoomsJob' logic
expect(mockStorage.read { db -> Int in try OpenGroup.fetchCount(db) }).to(equal(2))
expect(
mockStorage.read { db -> String? in
try OpenGroup
.select(.server)
.asRequest(of: String.self)
.fetchOne(db)
}
).to(equal("https://open.getsession.org"))
expect(
mockStorage.read { db -> String? in
try OpenGroup
.select(.publicKey)
.asRequest(of: String.self)
.fetchOne(db)
}
).to(equal("a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238"))
expect(
mockStorage.read { db -> Bool? in
try OpenGroup
.select(.isActive)
.asRequest(of: Bool.self)
.fetchOne(db)
}
).to(beFalse())
}
// MARK: ---- fetches rooms for the server
it("fetches rooms for the server") {
var response: [OpenGroupManager.DefaultRoomInfo]?
openGroupManager.getDefaultRoomsIfNeeded()
.handleEvents(receiveOutput: { response = $0 })
.sinkAndStore(in: &disposables)
expect(response?.map { $0.room })
.to(equal([OpenGroupAPI.Room.mock]))
}
// MARK: ---- will retry fetching rooms 8 times before it fails
it("will retry fetching rooms 8 times before it fails") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.nullResponse())
var error: Error?
openGroupManager.getDefaultRoomsIfNeeded()
.mapError { result -> Error in error.setting(to: result) }
.sinkAndStore(in: &disposables)
expect(error).to(matchError(NetworkError.parsingFailed))
expect(mockNetwork) // First attempt + 8 retries
.to(call(.exactly(times: 9)) { network in
network.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any)
expect(mockNetwork)
.to(call { network in
network.send(
expectedRequest.body,
to: expectedRequest.destination,
requestTimeout: expectedRequest.requestTimeout,
requestAndPathBuildTimeout: expectedRequest.requestAndPathBuildTimeout
)
})
}
// MARK: ---- removes the cache publisher if all retries fail
it("removes the cache publisher if all retries fail") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(MockNetwork.nullResponse())
var error: Error?
openGroupManager.getDefaultRoomsIfNeeded()
.mapError { result -> Error in error.setting(to: result) }
.sinkAndStore(in: &disposables)
// MARK: ---- does not start a job to retrieve the default rooms if we already have rooms
it("does not start a job to retrieve the default rooms if we already have rooms") {
mockAppGroupDefaults.when { $0.bool(forKey: UserDefaults.BoolKey.isMainAppActive.rawValue) }.thenReturn(true)
cache.setDefaultRoomInfo([(room: OpenGroupAPI.Room.mock, openGroup: OpenGroup.mock)])
cache.defaultRoomsPublisher.sinkUntilComplete()
expect(error)
.to(matchError(NetworkError.parsingFailed))
expect(mockOGMCache)
.to(call(matchingParameters: .all) {
$0.defaultRoomsPublisher = nil
})
}
// MARK: ---- schedules jobs to download any room images
it("schedules jobs to download any room images") {
mockNetwork
.when { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) }
.thenReturn(
MockNetwork.batchResponseData(
with: [
(OpenGroupAPI.Endpoint.capabilities, OpenGroupAPI.Capabilities.mockBatchSubResponse()),
(
OpenGroupAPI.Endpoint.rooms,
[
OpenGroupAPI.Room.mock.with(
token: "test2",
name: "test2",
infoUpdates: 11,
imageId: "12"
)
].batchSubResponse()
)
]
)
)
openGroupManager
.getDefaultRoomsIfNeeded()
.sinkAndStore(in: &disposables)
expect(mockJobRunner)
.to(call(matchingParameters: .all) {
$0.add(
.any,
job: Job(
variant: .displayPictureDownload,
shouldBeUnique: true,
details: DisplayPictureDownloadJob.Details(
target: .community(
imageId: "12",
roomToken: "test2",
server: OpenGroupAPI.defaultServer
),
timestamp: 1234567890
)
),
dependantJob: nil,
canStartJob: true
)
})
expect(mockNetwork)
.toNot(call { $0.send(.any, to: .any, requestTimeout: .any, requestAndPathBuildTimeout: .any) })
}
}
}
@ -3014,6 +2868,18 @@ extension OpenGroupAPI.RoomPollInfo {
// MARK: - Mock Types
extension OpenGroup: Mocked {
static var mock: OpenGroup = OpenGroup(
server: "testserver",
roomToken: "testRoom",
publicKey: TestConstants.serverPublicKey,
isActive: true,
name: "testRoom",
userCount: 0,
infoUpdates: 0
)
}
extension OpenGroupAPI.Capabilities: Mocked {
static var mock: OpenGroupAPI.Capabilities = OpenGroupAPI.Capabilities(capabilities: [], missing: nil)
}

@ -77,9 +77,6 @@ class CommunityPollerSpec: QuickSpec {
)
@TestState(cache: .openGroupManager, in: dependencies) var mockOGMCache: MockOGMCache! = MockOGMCache(
initialSetup: { cache in
cache
.when { $0.defaultRoomsPublisher = .any(type: [OpenGroupManager.DefaultRoomInfo].self) }
.thenReturn(())
cache.when { $0.pendingChanges }.thenReturn([])
cache.when { $0.getTimeSinceLastOpen(using: .any) }.thenReturn(0)
}

@ -7,9 +7,8 @@ import SessionUtilitiesKit
@testable import SessionMessagingKit
class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? {
get { return mock() }
set { mockNoReturn(args: [newValue]) }
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> {
mock()
}
var pendingChanges: [OpenGroupAPI.PendingChange] {
@ -20,4 +19,8 @@ class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
return mock(args: [dependencies])
}
func setDefaultRoomInfo(_ info: [OpenGroupManager.DefaultRoomInfo]) {
mockNoReturn(args: [info])
}
}

@ -107,7 +107,7 @@ public extension Network {
code: Int,
headers: [String: String] = [:],
body: T? = nil,
failedToParseBody: Bool = false
failedToParseBody: Bool
) {
self.code = code
self.headers = headers
@ -151,6 +151,7 @@ extension Network.BatchSubResponse: Decodable {
protocol ErasedBatchSubResponse: ResponseInfoType {
var erasedBody: Any? { get }
var failedToParseBody: Bool { get }
}
// MARK: - Convenience

@ -362,7 +362,8 @@ extension Network.PreparedRequest: ErasedPreparedRequest {
return Network.BatchSubResponse(
code: subResponse.code,
headers: subResponse.headers,
body: try originalType.from(subResponse.erasedBody).map { try converter(info, $0) }
body: try originalType.from(subResponse.erasedBody).map { try converter(info, $0) },
failedToParseBody: subResponse.failedToParseBody
)
default: return try originalType.from(data).map { try converter(info, $0) } as Any

Loading…
Cancel
Save