Fixed a few issues with the JobRunner

Updated the JobRunner to support dependency injection
Updated the DataExtractionNotification to take a 'sentTimestamp' when created to reduce the chance for duplicates being sent
Fixed an issue where checking current and pending jobs wasn't including blocking jobs
Fixed an issue where the 'hasPendingOrRunningJob' check didn't actually include running jobs
Fixed some odd behaviours with job dependencies
Fixed an incorrect failure count check
pull/813/head
Morgan Pretty 1 year ago
parent cd00975e56
commit ffdc59b704

@ -242,6 +242,6 @@ SPEC CHECKSUMS:
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 2bf7639359fecebe56e9757d88f4eb48864652d2
PODFILE CHECKSUM: 97324ae5888b01db2f2adc4dcc239e2e7d6867f7
COCOAPODS: 1.11.3

@ -761,6 +761,10 @@
FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; };
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; };
FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; };
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */; };
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
FD96F3A829DBD4AD00401309 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
FD96F3A929DBD4AD00401309 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
FDA8EAFE280E8B78002B68E5 /* FailedMessageSendsJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA8EAFD280E8B78002B68E5 /* FailedMessageSendsJob.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 */; };
@ -1841,6 +1845,8 @@
FD87DD0328B8727D00AF0F98 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.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>"; };
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendJobSpec.swift; sourceTree = "<group>"; };
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockJobRunner.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>"; };
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = "<group>"; };
@ -3928,6 +3934,7 @@
children = (
FDC290A527D860CE005DAE71 /* Mock.swift */,
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
@ -3970,6 +3977,22 @@
path = JobRunner;
sourceTree = "<group>";
};
FD96F3A229DBC3BA00401309 /* Jobs */ = {
isa = PBXGroup;
children = (
FD96F3A329DBC3D000401309 /* Types */,
);
path = Jobs;
sourceTree = "<group>";
};
FD96F3A329DBC3D000401309 /* Types */ = {
isa = PBXGroup;
children = (
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */,
);
path = Types;
sourceTree = "<group>";
};
FDC2909227D710A9005DAE71 /* Types */ = {
isa = PBXGroup;
children = (
@ -4059,6 +4082,7 @@
FDC4389B27BA01E300C60D73 /* _TestUtilities */,
FD3C905D27E410DB00CD579F /* Common Networking */,
FD3C906527E416A200CD579F /* Contacts */,
FD96F3A229DBC3BA00401309 /* Jobs */,
FDC4389827BA001800C60D73 /* Open Groups */,
FD3C906B27E43C2400CD579F /* Sending & Receiving */,
FD3C906827E417B100CD579F /* Utilities */,
@ -5794,6 +5818,7 @@
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */,
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
FD96F3A829DBD4AD00401309 /* MockJobRunner.swift in Sources */,
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
@ -5807,6 +5832,7 @@
buildActionMask = 2147483647;
files = (
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */,
FD96F3A929DBD4AD00401309 /* MockJobRunner.swift in Sources */,
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
@ -5827,9 +5853,11 @@
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */,
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */,
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,

@ -2015,7 +2015,8 @@ extension ConversationVC:
try MessageSender.send(
db,
message: DataExtractionNotification(
kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs))
kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)),
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
),
interactionId: nil,
in: thread
@ -2269,7 +2270,8 @@ extension ConversationVC:
try MessageSender.send(
db,
message: DataExtractionNotification(
kind: .screenshot
kind: .screenshot,
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
),
interactionId: nil,
in: thread

@ -540,7 +540,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
message: DataExtractionNotification(
kind: .mediaSaved(
timestamp: UInt64(currentViewController.galleryItem.interactionTimestampMs)
)
),
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
),
interactionId: nil, // Show no interaction for the current user
in: thread

@ -254,7 +254,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
private func completePostMigrationSetup(needsConfigSync: Bool) {
Configuration.performMainSetup()
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
JobRunner.setExecutor(SyncPushTokensJob.self, for: .syncPushTokens)
/// Setup the UI
///

@ -34,17 +34,17 @@ public enum SNMessagingKit { // Just to make the external API nice
public static func configure() {
// Configure the job executors
JobRunner.add(executor: DisappearingMessagesJob.self, for: .disappearingMessages)
JobRunner.add(executor: FailedMessageSendsJob.self, for: .failedMessageSends)
JobRunner.add(executor: FailedAttachmentDownloadsJob.self, for: .failedAttachmentDownloads)
JobRunner.add(executor: UpdateProfilePictureJob.self, for: .updateProfilePicture)
JobRunner.add(executor: RetrieveDefaultOpenGroupRoomsJob.self, for: .retrieveDefaultOpenGroupRooms)
JobRunner.add(executor: GarbageCollectionJob.self, for: .garbageCollection)
JobRunner.add(executor: MessageSendJob.self, for: .messageSend)
JobRunner.add(executor: MessageReceiveJob.self, for: .messageReceive)
JobRunner.add(executor: NotifyPushServerJob.self, for: .notifyPushServer)
JobRunner.add(executor: SendReadReceiptsJob.self, for: .sendReadReceipts)
JobRunner.add(executor: AttachmentDownloadJob.self, for: .attachmentDownload)
JobRunner.add(executor: AttachmentUploadJob.self, for: .attachmentUpload)
JobRunner.setExecutor(DisappearingMessagesJob.self, for: .disappearingMessages)
JobRunner.setExecutor(FailedMessageSendsJob.self, for: .failedMessageSends)
JobRunner.setExecutor(FailedAttachmentDownloadsJob.self, for: .failedAttachmentDownloads)
JobRunner.setExecutor(UpdateProfilePictureJob.self, for: .updateProfilePicture)
JobRunner.setExecutor(RetrieveDefaultOpenGroupRoomsJob.self, for: .retrieveDefaultOpenGroupRooms)
JobRunner.setExecutor(GarbageCollectionJob.self, for: .garbageCollection)
JobRunner.setExecutor(MessageSendJob.self, for: .messageSend)
JobRunner.setExecutor(MessageReceiveJob.self, for: .messageReceive)
JobRunner.setExecutor(NotifyPushServerJob.self, for: .notifyPushServer)
JobRunner.setExecutor(SendReadReceiptsJob.self, for: .sendReadReceipts)
JobRunner.setExecutor(AttachmentDownloadJob.self, for: .attachmentDownload)
JobRunner.setExecutor(AttachmentUploadJob.self, for: .attachmentUpload)
}
}

@ -42,8 +42,8 @@ public enum AttachmentDownloadJob: JobExecutor {
// the same attachment multiple times at the same time (it also adds a "clean up" mechanism
// if an attachment ends up stuck in a "downloading" state incorrectly
guard attachment.state != .downloading else {
let otherCurrentJobAttachmentIds: Set<String> = JobRunner
.detailsForCurrentlyRunningJobs(of: .attachmentDownload)
let otherCurrentJobAttachmentIds: Set<String> = dependencies.jobRunner
.detailsFor(state: .running, variant: .attachmentDownload)
.filter { key, _ in key != job.id }
.values
.compactMap { data -> String? in

@ -24,7 +24,7 @@ public enum MessageSendJob: JobExecutor {
let detailsData: Data = job.details,
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
else {
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
return
}
@ -37,7 +37,7 @@ public enum MessageSendJob: JobExecutor {
let jobId: Int64 = job.id,
let interactionId: Int64 = job.interactionId
else {
failure(job, JobRunnerError.missingRequiredDetails, false, dependencies)
failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
return
}
@ -89,16 +89,16 @@ public enum MessageSendJob: JobExecutor {
}
.filter { stateInfo in
// Don't add a new job if there is one already in the queue
!JobRunner.hasPendingOrRunningJob(
with: .attachmentUpload,
details: AttachmentUploadJob.Details(
!dependencies.jobRunner.hasJob(
of: .attachmentUpload,
with: AttachmentUploadJob.Details(
messageSendJobId: jobId,
attachmentId: stateInfo.attachmentId
)
)
}
.compactMap { stateInfo -> (jobId: Int64, job: Job)? in
JobRunner
dependencies.jobRunner
.insert(
db,
job: Job(
@ -131,8 +131,8 @@ public enum MessageSendJob: JobExecutor {
let hasPendingUploads: Bool = allAttachmentStateInfo.contains(where: { $0.state != .uploaded })
return (
(isMissingFileIds && !hasPendingUploads),
hasPendingUploads,
(isMissingFileIds && !hasPendingUploads), // shouldFail
hasPendingUploads, // shouldDefer
fileIds
)
}

@ -27,8 +27,13 @@ public final class DataExtractionNotification: ControlMessage {
// MARK: - Initialization
public init(kind: Kind) {
super.init()
public init(
kind: Kind,
sentTimestamp: UInt64? = nil
) {
super.init(
sentTimestamp: sentTimestamp
)
self.kind = kind
}

@ -0,0 +1,415 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Quick
import Nimble
@testable import SessionMessagingKit
@testable import SessionUtilitiesKit
class MessageSendJobSpec: QuickSpec {
// MARK: - Spec
override func spec() {
var job: Job!
var interaction: Interaction!
var attachment1: Attachment!
var interactionAttachment1: InteractionAttachment!
var mockStorage: Storage!
var mockJobRunner: MockJobRunner!
var dependencies: Dependencies!
// MARK: - JobRunner
describe("a MessageSendJob") {
beforeEach {
mockStorage = Storage(
customWriter: try! DatabaseQueue(),
customMigrations: [
SNUtilitiesKit.migrations(),
SNMessagingKit.migrations()
]
)
mockJobRunner = MockJobRunner()
dependencies = Dependencies(
storage: mockStorage,
jobRunner: mockJobRunner,
date: Date(timeIntervalSince1970: 1234567890)
)
attachment1 = Attachment(
id: "200",
variant: .standard,
state: .failedDownload,
contentType: "text/plain",
byteCount: 200
)
mockStorage.write { db in
try SessionThread.fetchOrCreate(db, id: "Test1", variant: .contact)
}
mockJobRunner
.when {
$0.hasJob(
of: any(),
inState: .running,
with: AttachmentUploadJob.Details(
messageSendJobId: 1,
attachmentId: attachment1.id
)
)
}
.thenReturn(false)
mockJobRunner
.when { $0.insert(any(), job: any(), before: any(), dependencies: dependencies) }
.then { args in
let db: Database = args[0] as! Database
var job: Job = args[1] as! Job
job.id = 1000
try! job.insert(db)
}
.thenReturn((1000, Job(variant: .messageSend)))
}
afterEach {
job = nil
mockStorage = nil
dependencies = nil
}
it("fails when not given any details") {
job = Job(variant: .messageSend)
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
expect(permanentFailure).to(beTrue())
}
it("fails when not given incorrect details") {
job = Job(
variant: .messageSend,
details: MessageReceiveJob.Details(messages: [], calledFromBackgroundPoller: false)
)
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
expect(permanentFailure).to(beTrue())
}
context("of VisibleMessage") {
beforeEach {
interaction = Interaction(
id: 100,
serverHash: nil,
messageUuid: nil,
threadId: "Test1",
authorId: "Test",
variant: .standardOutgoing,
body: "Test",
timestampMs: 1234567890,
receivedAtTimestampMs: 1234567900,
wasRead: false,
hasMention: false,
expiresInSeconds: nil,
expiresStartedAtMs: nil,
linkPreviewUrl: nil,
openGroupServerMessageId: nil,
openGroupWhisperMods: false,
openGroupWhisperTo: nil
)
job = Job(
variant: .messageSend,
interactionId: interaction.id!,
details: MessageSendJob.Details(
destination: .contact(publicKey: "Test"),
message: VisibleMessage(
text: "Test"
)
)
)
mockStorage.write { db in
try interaction.insert(db)
try job.insert(db)
}
}
it("fails when there is no job id") {
job = Job(
variant: .messageSend,
interactionId: interaction.id!,
details: MessageSendJob.Details(
destination: .contact(publicKey: "Test"),
message: VisibleMessage(
text: "Test"
)
)
)
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
expect(permanentFailure).to(beTrue())
}
it("fails when there is no interaction id") {
job = Job(
variant: .messageSend,
details: MessageSendJob.Details(
destination: .contact(publicKey: "Test"),
message: VisibleMessage(
text: "Test"
)
)
)
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
expect(permanentFailure).to(beTrue())
}
it("fails when there is no interaction for the provided interaction id") {
job = Job(
variant: .messageSend,
interactionId: 12345,
details: MessageSendJob.Details(
destination: .contact(publicKey: "Test"),
message: VisibleMessage(
text: "Test"
)
)
)
mockStorage.write { db in try job.insert(db) }
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(StorageError.objectNotFound))
expect(permanentFailure).to(beTrue())
}
context("with an attachment") {
beforeEach {
interactionAttachment1 = InteractionAttachment(
albumIndex: 0,
interactionId: interaction.id!,
attachmentId: attachment1.id
)
mockStorage.write { db in
try attachment1.insert(db)
try interactionAttachment1.insert(db)
}
}
it("it fails when trying to send with an attachment which previously failed to download") {
mockStorage.write { db in
try attachment1.with(state: .failedDownload).save(db)
}
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(AttachmentError.notUploaded))
expect(permanentFailure).to(beTrue())
}
it("it fails when trying to send with an attachment that has an invalid downloadUrl") {
mockStorage.write { db in
try attachment1
.with(
state: .uploaded,
downloadUrl: nil
)
.save(db)
}
var error: Error? = nil
var permanentFailure: Bool = false
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, runError, runPermanentFailure, _ in
error = runError
permanentFailure = runPermanentFailure
},
deferred: { _, _ in },
dependencies: dependencies
)
expect(error).to(matchError(AttachmentError.notUploaded))
expect(permanentFailure).to(beTrue())
}
context("with a pending upload") {
beforeEach {
mockStorage.write { db in
try attachment1.with(state: .uploading).save(db)
}
}
it("it defers when trying to send with an attachment which is still pending upload") {
var didDefer: Bool = false
mockStorage.write { db in
try attachment1.with(state: .uploading).save(db)
}
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, _, _, _ in },
deferred: { _, _ in didDefer = true },
dependencies: dependencies
)
expect(didDefer).to(beTrue())
}
it("inserts an attachment upload job before the message send job") {
mockJobRunner
.when {
$0.hasJob(
of: any(),
inState: .running,
with: AttachmentUploadJob.Details(
messageSendJobId: 1,
attachmentId: "200"
)
)
}
.thenReturn(false)
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, _, _, _ in },
deferred: { _, _ in },
dependencies: dependencies
)
expect(mockJobRunner)
.to(call(.exactly(times: 1), matchingParameters: true) {
$0.insert(
any(),
job: Job(
variant: .attachmentUpload,
behaviour: .runOnce,
shouldBlock: false,
shouldSkipLaunchBecomeActive: false,
interactionId: 100,
details: AttachmentUploadJob.Details(
messageSendJobId: 1,
attachmentId: "200"
)
),
before: job,
dependencies: dependencies
)
})
}
it("creates a dependency between the new job and the existing one") {
MessageSendJob.run(
job,
queue: .main,
success: { _, _, _ in },
failure: { _, _, _, _ in },
deferred: { _, _ in },
dependencies: dependencies
)
expect(mockStorage.read { db in try JobDependencies.fetchOne(db) })
.to(equal(JobDependencies(jobId: 9, dependantId: 1000)))
}
}
}
}
}
}
}

@ -3568,11 +3568,11 @@ class OpenGroupManagerSpec: QuickSpec {
it("adds the image retrieval promise to the cache") {
class TestNeverReturningApi: OnionRequestAPIType {
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String, timeout: TimeInterval) -> Promise<(OnionRequestResponseInfoType, Data?)> {
return Promise<(OnionRequestResponseInfoType, Data?)>.pending().promise
}
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> {
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?, timeout: TimeInterval) -> Promise<Data> {
return Promise.value(Data())
}
}

@ -34,7 +34,7 @@ class TestOnionRequestAPI: OnionRequestAPIType {
class var mockResponse: Data? { return nil }
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
static func sendOnionRequest(_ request: URLRequest, to server: String, using version: OnionRequestAPIVersion, with x25519PublicKey: String, timeout: TimeInterval) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let responseInfo: ResponseInfo = ResponseInfo(
requestData: RequestData(
urlString: request.url?.absoluteString,
@ -54,7 +54,7 @@ class TestOnionRequestAPI: OnionRequestAPIType {
return Promise.value((responseInfo, mockResponse))
}
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?) -> Promise<Data> {
static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPIEndpoint, with parameters: JSON, associatedWith publicKey: String?, timeout: TimeInterval) -> Promise<Data> {
return Promise.value(mockResponse!)
}
}

@ -24,6 +24,6 @@ public enum SNSnodeKit { // Just to make the external API nice
public static func configure() {
// Configure the job executors
JobRunner.add(executor: GetSnodePoolJob.self, for: .getSnodePool)
JobRunner.setExecutor(GetSnodePoolJob.self, for: .getSnodePool)
}
}

@ -3,7 +3,7 @@
import Foundation
import GRDB
public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
public struct Job: Codable, Hashable, Equatable, Identifiable, FetchableRecord, MutablePersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "job" }
internal static let dependencyForeignKey = ForeignKey([Columns.id], to: [JobDependencies.Columns.dependantId])
public static let dependantJobDependency = hasMany(
@ -184,7 +184,7 @@ public struct Job: Codable, Equatable, Identifiable, FetchableRecord, MutablePer
// MARK: - Initialization
fileprivate init(
internal init(
id: Int64?,
failureCount: UInt,
variant: Variant,

@ -3,7 +3,7 @@
import Foundation
import GRDB
public struct JobDependencies: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public struct JobDependencies: Codable, Hashable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "jobDependencies" }
internal static let jobForeignKey = ForeignKey([Columns.jobId], to: [Job.Columns.id])
internal static let dependantForeignKey = ForeignKey([Columns.dependantId], to: [Job.Columns.id])

@ -45,6 +45,14 @@ public extension Array {
return updatedArray
}
func inserting(contentsOf other: [Element]?, at index: Int) -> [Element] {
guard let other: [Element] = other else { return self }
var updatedArray: [Element] = self
updatedArray.insert(contentsOf: other, at: 0)
return updatedArray
}
func grouped<Key: Hashable>(by keyForValue: (Element) throws -> Key) -> [Key: [Element]] {
return ((try? Dictionary(grouping: self, by: keyForValue)) ?? [:])
}

@ -16,6 +16,12 @@ open class Dependencies {
set { _storage.mutate { $0 = newValue } }
}
public var _jobRunner: Atomic<JobRunnerType?>
public var jobRunner: JobRunnerType {
get { Dependencies.getValueSettingIfNull(&_jobRunner) { JobRunner.instance } }
set { _jobRunner.mutate { $0 = newValue } }
}
public var _scheduler: Atomic<ValueObservationScheduler?>
public var scheduler: ValueObservationScheduler {
get { Dependencies.getValueSettingIfNull(&_scheduler) { Storage.defaultPublisherScheduler } }
@ -39,12 +45,14 @@ open class Dependencies {
public init(
generalCache: Atomic<GeneralCacheType>? = nil,
storage: Storage? = nil,
jobRunner: JobRunnerType? = nil,
scheduler: ValueObservationScheduler? = nil,
standardUserDefaults: UserDefaultsType? = nil,
date: Date? = nil
) {
_generalCache = Atomic(generalCache)
_storage = Atomic(storage)
_jobRunner = Atomic(jobRunner)
_scheduler = Atomic(scheduler)
_standardUserDefaults = Atomic(standardUserDefaults)
_date = Atomic(date)

@ -3,6 +3,12 @@
import Foundation
public extension Set {
mutating func insert(contentsOf value: Set<Element>?) {
guard let value: Set<Element> = value else { return }
value.forEach { self.insert($0) }
}
func inserting(_ value: Element?) -> Set<Element> {
guard let value: Element = value else { return self }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,8 +1,10 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Sodium
import Curve25519Kit
import SessionUtilitiesKit
extension Box.KeyPair: Mocked {
static var mockValue: Box.KeyPair = Box.KeyPair(
@ -19,3 +21,19 @@ extension ECKeyPair: Mocked {
)
}
}
extension Database: Mocked {
static var mockValue: Database {
var result: Database!
try! DatabaseQueue().read { result = $0 }
return result!
}
}
extension Job: Mocked {
static var mockValue: Job = Job(variant: .messageSend)
}
extension Job.Variant: Mocked {
static var mockValue: Job.Variant = .messageSend
}

@ -0,0 +1,56 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtilitiesKit
@testable import SessionMessagingKit
class MockJobRunner: Mock<JobRunnerType>, JobRunnerType {
// MARK: - Configuration
func setExecutor(_ executor: JobExecutor.Type, for variant: Job.Variant) {
accept(args: [executor, variant])
}
func canStart(queue: JobQueue) -> Bool {
return accept(args: [queue]) as! Bool
}
// MARK: - State Management
func isCurrentlyRunning(_ job: Job?) -> Bool {
return accept(args: [job]) as! Bool
}
func hasJob<T: Encodable>(of variant: Job.Variant, inState state: JobRunner.JobState, with jobDetails: T) -> Bool {
return accept(args: [variant, state, jobDetails]) as! Bool
}
func detailsFor(jobs: [Job]?, state: JobRunner.JobState, variant: Job.Variant?) -> [Int64: Data?] {
return accept(args: [jobs, state, variant]) as! [Int64: Data?]
}
func appDidFinishLaunching(dependencies: Dependencies) {}
func appDidBecomeActive(dependencies: Dependencies) {}
func startNonBlockingQueues(dependencies: Dependencies) {}
func stopAndClearPendingJobs(exceptForVariant: Job.Variant?, onComplete: (() -> ())?) {
accept(args: [exceptForVariant, onComplete])
onComplete?()
}
// MARK: - Job Scheduling
func add(_ db: Database, job: Job?, canStartJob: Bool, dependencies: Dependencies) {
accept(args: [db, job, canStartJob])
}
func upsert(_ db: Database, job: Job?, canStartJob: Bool, dependencies: Dependencies) {
accept(args: [db, job, canStartJob])
}
func insert(_ db: Database, job: Job?, before otherJob: Job, dependencies: Dependencies) -> (Int64, Job)? {
return accept(args: [db, job, otherJob]) as? (Int64, Job)
}
}
Loading…
Cancel
Save